blob: 5a3478045f8e1166a065f73f8c6dd6f32bdaa4b3 [file]
// 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::InternalTouchEvent;
using scenic_impl::input::StreamId;
constexpr view_tree::BoundingBox kViewBoundsEmpty{};
constexpr bool kStreamOngoing = false;
constexpr bool kStreamEnding = true;
TEST(A11yLegacyContenderTest, SingleStream_ConsumedAtSweep) {
constexpr StreamId kId1 = 1;
constexpr uint32_t kPointerId1 = 4;
std::vector<GestureResponse> responses;
std::vector<InternalTouchEvent> events_sent_to_client;
scenic_impl::input::GestureContenderInspector inspector(inspect::Node{});
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses.push_back(response); },
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalTouchEvent& event) {
events_sent_to_client.emplace_back(event.ShallowClone());
},
inspector);
// 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);
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
EXPECT_EQ(events_sent_to_client.size(), 1u);
EXPECT_TRUE(responses.empty());
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
EXPECT_EQ(events_sent_to_client.size(), 2u);
EXPECT_TRUE(responses.empty());
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamEnding, kViewBoundsEmpty);
}
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<InternalTouchEvent> events_sent_to_client;
scenic_impl::input::GestureContenderInspector inspector(inspect::Node{});
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses.push_back(response); },
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalTouchEvent& event) {
events_sent_to_client.emplace_back(event.ShallowClone());
},
inspector);
// 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);
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
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.
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
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);
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamEnding, kViewBoundsEmpty);
}
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<InternalTouchEvent> events_sent_to_client;
scenic_impl::input::GestureContenderInspector inspector(inspect::Node{});
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses.push_back(response); },
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalTouchEvent& event) {
events_sent_to_client.emplace_back(event.ShallowClone());
},
inspector);
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
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<InternalTouchEvent> events_sent_to_client;
A11yLegacyContender* contender_ptr;
scenic_impl::input::GestureContenderInspector inspector(inspect::Node{});
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 InternalTouchEvent& event) {
events_sent_to_client.emplace_back(event.ShallowClone());
},
inspector);
contender_ptr = &contender;
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
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();
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
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<InternalTouchEvent> events_sent_to_client;
scenic_impl::input::GestureContenderInspector inspector(inspect::Node{});
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses[id].push_back(response); },
/*deliver_events_to_client*/
[&events_sent_to_client](const InternalTouchEvent& event) {
events_sent_to_client.emplace_back(event.ShallowClone());
},
inspector);
// Start three streams and make sure they're all handled correctly individually.
EXPECT_EQ(events_sent_to_client.size(), 0u);
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
EXPECT_EQ(events_sent_to_client.size(), 2u);
EXPECT_TRUE(responses.empty());
{
InternalTouchEvent event;
event.pointer_id = kPointerId2;
contender.UpdateStream(kId2, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId2;
contender.UpdateStream(kId2, std::move(event), kStreamEnding, kViewBoundsEmpty);
}
EXPECT_EQ(events_sent_to_client.size(), 4u);
EXPECT_TRUE(responses.empty());
{
InternalTouchEvent event;
event.pointer_id = kPointerId3;
contender.UpdateStream(kId3, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId3;
contender.UpdateStream(kId3, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
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.
{
InternalTouchEvent event;
event.pointer_id = kPointerId2;
contender.UpdateStream(kId2, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
EXPECT_EQ(events_sent_to_client.size(), 1u);
EXPECT_TRUE(responses.empty());
{
InternalTouchEvent event;
event.pointer_id = kPointerId3;
contender.UpdateStream(kId3, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
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.
{
InternalTouchEvent event;
event.pointer_id = kPointerId1;
contender.UpdateStream(kId1, std::move(event), kStreamOngoing, kViewBoundsEmpty);
}
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;
scenic_impl::input::GestureContenderInspector inspector(inspect::Node{});
auto contender = A11yLegacyContender(
/*respond*/
[&responses](StreamId id, GestureResponse response) { responses[id].push_back(response); },
/*deliver_events_to_client*/
[](const InternalTouchEvent& event) {}, inspector);
// Create three streams and end them.
{
InternalTouchEvent event;
event.pointer_id = kPointerId;
contender.UpdateStream(kId1, std::move(event), kStreamEnding, {});
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId;
contender.UpdateStream(kId2, std::move(event), kStreamEnding, {});
}
{
InternalTouchEvent event;
event.pointer_id = kPointerId;
contender.UpdateStream(kId3, std::move(event), kStreamEnding, {});
}
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);
}
} // namespace
} // namespace lib_ui_input_tests