blob: 26061a75d90107f2fe7a45354348105dac63f143 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/scenic/lib/input/a11y_legacy_contender.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace lib_ui_input_tests {
namespace {
using scenic_impl::input::A11yLegacyContender;
using scenic_impl::input::GestureResponse;
using scenic_impl::input::InternalPointerEvent;
using scenic_impl::input::StreamId;
TEST(A11yLegacyContenderTest, SingleStream_ConsumedAtSweep) {
constexpr StreamId kId1 = 1;
constexpr uint32_t kPointerId1 = 4;
std::vector<GestureResponse> responses;
std::vector<InternalPointerEvent> events_sent_to_client;
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses.push_back(response); },
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalPointerEvent& event) {
events_sent_to_client.emplace_back(event);
});
// Start a stream. No events shuld get responses until the client makes a decision,
// and all events should be forwarded to client.
EXPECT_EQ(events_sent_to_client.size(), 0u);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 1u);
EXPECT_TRUE(responses.empty());
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 2u);
EXPECT_TRUE(responses.empty());
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ true);
EXPECT_EQ(events_sent_to_client.size(), 3u);
EXPECT_TRUE(responses.empty());
contender.OnStreamHandled(kPointerId1,
fuchsia::ui::input::accessibility::EventHandling::CONSUMED);
ASSERT_EQ(responses.size(), 3u);
EXPECT_THAT(responses, testing::Each(GestureResponse::kYesPrioritize));
// Award the win. Expect no more responses.
responses.clear();
events_sent_to_client.clear();
contender.EndContest(kId1, /*awarded_win*/ true);
EXPECT_TRUE(events_sent_to_client.empty());
EXPECT_TRUE(responses.empty());
}
TEST(A11yLegacyContenderTest, SingleStream_ConsumedMidContest) {
constexpr StreamId kId1 = 1;
constexpr uint32_t kPointerId1 = 4;
std::vector<GestureResponse> responses;
std::vector<InternalPointerEvent> events_sent_to_client;
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses.push_back(response); },
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalPointerEvent& event) {
events_sent_to_client.emplace_back(event);
});
// Start a stream. No events should get responses until the client makes a decision,
// and all events should be forwarded to client.
EXPECT_EQ(events_sent_to_client.size(), 0u);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 2u);
EXPECT_TRUE(responses.empty());
// Since the stream hasn't ended yet we're not at sweep, but the YES_PRIORITIZE response is sent
// immediately.
contender.OnStreamHandled(kPointerId1,
fuchsia::ui::input::accessibility::EventHandling::CONSUMED);
ASSERT_EQ(responses.size(), 2u);
EXPECT_THAT(responses, testing::Each(GestureResponse::kYesPrioritize));
// Subsequent events should have a YES response.
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
ASSERT_EQ(responses.size(), 3u);
EXPECT_EQ(responses[2], GestureResponse::kYesPrioritize);
// Award the win. Expect no responses on subsequent events.
responses.clear();
events_sent_to_client.clear();
contender.EndContest(kId1, /*awarded_win*/ true);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ true);
EXPECT_EQ(events_sent_to_client.size(), 2u);
EXPECT_TRUE(responses.empty());
}
TEST(A11yLegacyContenderTest, SingleStream_Rejected) {
constexpr StreamId kId1 = 1;
constexpr uint32_t kPointerId1 = 4;
std::vector<GestureResponse> responses;
std::vector<InternalPointerEvent> events_sent_to_client;
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses.push_back(response); },
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalPointerEvent& event) {
events_sent_to_client.emplace_back(event);
});
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 2u);
EXPECT_TRUE(responses.empty());
// On rejection we should get single NO response.
contender.OnStreamHandled(kPointerId1,
fuchsia::ui::input::accessibility::EventHandling::REJECTED);
ASSERT_EQ(responses.size(), 1u);
EXPECT_EQ(responses[0], GestureResponse::kNo);
}
// Tests that no further responses are sent after the contest ends.
TEST(A11yLegacyContenderTest, ContestEndedOnResponse) {
constexpr StreamId kId1 = 1;
constexpr uint32_t kPointerId1 = 4;
std::vector<GestureResponse> responses;
std::vector<InternalPointerEvent> events_sent_to_client;
A11yLegacyContender* contender_ptr;
auto contender = A11yLegacyContender(
/*respond*/
[&responses, &contender_ptr](StreamId id, GestureResponse response) {
responses.push_back(response);
contender_ptr->EndContest(id, /*awarded_win*/ true);
},
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalPointerEvent& event) {
events_sent_to_client.emplace_back(event);
});
contender_ptr = &contender;
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 3u);
EXPECT_TRUE(responses.empty());
// Consume the stream. The win is awarded on the first response, and no further responses
// should be seen.
contender.OnStreamHandled(kPointerId1,
fuchsia::ui::input::accessibility::EventHandling::CONSUMED);
ASSERT_EQ(responses.size(), 1u);
EXPECT_EQ(responses[0], GestureResponse::kYesPrioritize);
// Check that events are delivered after contest end.
events_sent_to_client.clear();
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 1u);
}
TEST(A11yLegacyContenderTest, MultipleStreams) {
constexpr StreamId kId1 = 1, kId2 = 2, kId3 = 3;
constexpr uint32_t kPointerId1 = 4, kPointerId2 = 5, kPointerId3 = 6;
std::unordered_map<StreamId, std::vector<GestureResponse>> responses;
std::vector<InternalPointerEvent> events_sent_to_client;
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses[id].push_back(response); },
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalPointerEvent& event) {
events_sent_to_client.emplace_back(event);
});
// Start three streams and make sure they're all handled correctly individually.
EXPECT_EQ(events_sent_to_client.size(), 0u);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 2u);
EXPECT_TRUE(responses.empty());
contender.UpdateStream(kId2, /*event*/ {.pointer_id = kPointerId2}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId2, /*event*/ {.pointer_id = kPointerId2}, /*is_end_of_stream*/ true);
EXPECT_EQ(events_sent_to_client.size(), 4u);
EXPECT_TRUE(responses.empty());
contender.UpdateStream(kId3, /*event*/ {.pointer_id = kPointerId3}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId3, /*event*/ {.pointer_id = kPointerId3}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 6u);
EXPECT_TRUE(responses.empty());
// Now the client decides on all three streams and observe the expected responses.
events_sent_to_client.clear();
contender.OnStreamHandled(kPointerId1,
fuchsia::ui::input::accessibility::EventHandling::CONSUMED);
EXPECT_EQ(responses.size(), 1u);
ASSERT_EQ(responses[kId1].size(), 2u);
EXPECT_THAT(responses[kId1], testing::Each(GestureResponse::kYesPrioritize));
contender.OnStreamHandled(kPointerId2,
fuchsia::ui::input::accessibility::EventHandling::CONSUMED);
EXPECT_EQ(responses.size(), 2u);
ASSERT_EQ(responses[kId2].size(), 2u);
EXPECT_THAT(responses[kId2], testing::Each(GestureResponse::kYesPrioritize));
contender.OnStreamHandled(kPointerId3,
fuchsia::ui::input::accessibility::EventHandling::REJECTED);
EXPECT_EQ(responses.size(), 3u);
ASSERT_EQ(responses[kId3].size(), 1u);
EXPECT_EQ(responses[kId3][0], GestureResponse::kNo);
EXPECT_EQ(events_sent_to_client.size(), 0u);
// End contests 2 and 3.
contender.EndContest(kId2, /*awarded_win*/ true);
contender.EndContest(kId3, /*awarded_win*/ false);
responses.clear();
// Since streams 2 and 3 ended and lost respectively they should count as new streams if used
// again.
contender.UpdateStream(kId2, /*event*/ {.pointer_id = kPointerId2}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 1u);
EXPECT_TRUE(responses.empty());
contender.UpdateStream(kId3, /*event*/ {.pointer_id = kPointerId3}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 2u);
EXPECT_TRUE(responses.empty());
// Stream 1 should continue to receive YES_PRIORITIZE on each new message, since that stream is
// still ongoing.
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ false);
EXPECT_EQ(events_sent_to_client.size(), 3u);
EXPECT_EQ(responses.size(), 1u);
EXPECT_EQ(responses[kId1][0], GestureResponse::kYesPrioritize);
}
// This test ensures that the contender can handle receiving multiple streams with the same
// pointer_id before a11y has time to respond.
TEST(A11yLegacyContenderTest, MultipleStreams_WithSamePointer) {
constexpr StreamId kId1 = 1, kId2 = 2, kId3 = 3;
constexpr uint32_t kPointerId = 4;
std::unordered_map<StreamId, std::vector<GestureResponse>> responses;
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses[id].push_back(response); },
/*deliver_events_to_client*/
[](auto) {});
// Create three streams and end them.
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId}, /*is_end_of_stream*/ true);
contender.UpdateStream(kId2, /*event*/ {.pointer_id = kPointerId}, /*is_end_of_stream*/ true);
contender.UpdateStream(kId3, /*event*/ {.pointer_id = kPointerId}, /*is_end_of_stream*/ true);
EXPECT_TRUE(responses.empty());
// Return OnStreamHandled messages for all ongoing streams, but always reuse kPointerId.
// Observe that each stream gets the correct message.
contender.OnStreamHandled(kPointerId, fuchsia::ui::input::accessibility::EventHandling::CONSUMED);
EXPECT_EQ(responses.size(), 1u);
ASSERT_EQ(responses[kId1].size(), 1u);
EXPECT_EQ(responses[kId1][0], GestureResponse::kYesPrioritize);
contender.OnStreamHandled(kPointerId, fuchsia::ui::input::accessibility::EventHandling::REJECTED);
EXPECT_EQ(responses.size(), 2u);
ASSERT_EQ(responses[kId2].size(), 1u);
EXPECT_EQ(responses[kId2][0], GestureResponse::kNo);
contender.OnStreamHandled(kPointerId, fuchsia::ui::input::accessibility::EventHandling::CONSUMED);
EXPECT_EQ(responses.size(), 3u);
ASSERT_EQ(responses[kId3].size(), 1u);
EXPECT_EQ(responses[kId3][0], GestureResponse::kYesPrioritize);
}
// Check that all ongoing streams (streams that either haven't been decided, or that were
// won but haven't ended yet) receive a NO response on destruction.
TEST(A11yLegacyContenderTest, EndOngoingStreamsOnDestruction) {
constexpr StreamId kId1 = 1, kId2 = 2, kId3 = 3, kId4 = 4, kId5 = 5, kId6 = 6;
constexpr uint32_t kPointerId1 = 6, kPointerId2 = 7, kPointerId3 = 8, kPointerId4 = 9,
kPointerId5 = 10, kPointerId6 = 11;
std::unordered_map<StreamId, std::vector<GestureResponse>> responses;
{
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses[id].push_back(response); },
/*deliver_events_to_client*/
[](auto) {});
// Starting 6 streams to test all combinations that cause ongoing or ended streams.
// Ended.
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId1}, /*is_end_of_stream*/ true);
contender.EndContest(kId1, /*awarded_win*/ true);
// Ended.
contender.UpdateStream(kId2, /*event*/ {.pointer_id = kPointerId2}, /*is_end_of_stream*/ true);
contender.EndContest(kId2, /*awarded_win*/ false);
// Ongoing.
contender.UpdateStream(kId3, /*event*/ {.pointer_id = kPointerId3}, /*is_end_of_stream*/ false);
contender.EndContest(kId3, /*awarded_win*/ true);
// Ended.
contender.UpdateStream(kId4, /*event*/ {.pointer_id = kPointerId4}, /*is_end_of_stream*/ false);
contender.EndContest(kId4, /*awarded_win*/ false);
// Ongoing.
contender.UpdateStream(kId5, /*event*/ {.pointer_id = kPointerId5}, /*is_end_of_stream*/ false);
// Stream ended, contest ongoing.
contender.UpdateStream(kId6, /*event*/ {.pointer_id = kPointerId6}, /*is_end_of_stream*/ false);
responses.clear();
}
// As |contender| goes out of scope, ongoing streams should receive a NO response.
EXPECT_EQ(responses.size(), 3u);
ASSERT_EQ(responses[kId3].size(), 1u);
EXPECT_EQ(responses[kId3][0], GestureResponse::kNo);
ASSERT_EQ(responses[kId5].size(), 1u);
EXPECT_EQ(responses[kId5][0], GestureResponse::kNo);
ASSERT_EQ(responses[kId6].size(), 1u);
EXPECT_EQ(responses[kId6][0], GestureResponse::kNo);
}
// Tests that contests ending out of order is cleaned up correctly.
TEST(A11yLegacyContenderTest, ContestsEndingOutOfOrder) {
constexpr StreamId kId1 = 1, kId2 = 2, kId3 = 3;
constexpr uint32_t kPointerId = 4;
std::unordered_map<StreamId, std::vector<GestureResponse>> responses;
{
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses[id].push_back(response); },
/*deliver_events_to_client*/
[](auto) {});
// Start three streams for the same pointer.
contender.UpdateStream(kId1, /*event*/ {.pointer_id = kPointerId}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId2, /*event*/ {.pointer_id = kPointerId}, /*is_end_of_stream*/ false);
contender.UpdateStream(kId3, /*event*/ {.pointer_id = kPointerId}, /*is_end_of_stream*/ false);
// Now end the second contest with a loss before any responses have been sent.
contender.EndContest(kId2, /*awarded_win*/ false);
}
// As |contender| goes out of scope, the two still ongoing streams should receive a NO response.
EXPECT_EQ(responses.size(), 2u);
ASSERT_EQ(responses[kId1].size(), 1u);
EXPECT_EQ(responses[kId1][0], GestureResponse::kNo);
ASSERT_EQ(responses[kId3].size(), 1u);
EXPECT_EQ(responses[kId3][0], GestureResponse::kNo);
}
} // namespace
} // namespace lib_ui_input_tests