blob: de1f0eb2a111030c03f5c1ecc2ce102ce21b8aa3 [file] [log] [blame]
// Copyright 2019 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/a11y/bin/a11y_manager/app.h"
#include <fuchsia/accessibility/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/gtest/test_loop_fixture.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <gtest/gtest.h>
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_color_transform_handler.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_focus_chain.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_pointer_event_registry.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_property_provider.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_semantic_listener.h"
#include "src/ui/a11y/bin/a11y_manager/tests/mocks/mock_setui_accessibility.h"
#include "src/ui/a11y/bin/a11y_manager/tests/util/util.h"
#include "src/ui/a11y/lib/annotation/tests/mocks/mock_annotation_view.h"
#include "src/ui/a11y/lib/gesture_manager/recognizers/one_finger_n_tap_recognizer.h"
#include "src/ui/a11y/lib/magnifier/tests/mocks/mock_magnification_handler.h"
#include "src/ui/a11y/lib/testing/input.h"
#include "src/ui/a11y/lib/util/util.h"
#include "src/ui/a11y/lib/view/tests/mocks/mock_view_semantics.h"
namespace accessibility_test {
namespace {
using fuchsia::accessibility::semantics::Node;
using fuchsia::accessibility::semantics::NodePtr;
using fuchsia::ui::input::accessibility::EventHandling;
using fuchsia::ui::input::accessibility::PointerEventListener;
using fuchsia::ui::input::accessibility::PointerEventListenerPtr;
class AppUnitTest : public gtest::TestLoopFixture {
public:
AppUnitTest()
: context_provider_(),
context_(context_provider_.context()),
mock_pointer_event_registry_(&context_provider_),
mock_color_transform_handler_(&context_provider_),
mock_setui_(&context_provider_),
mock_focus_chain_(&context_provider_),
mock_property_provider_(&context_provider_),
mock_annotation_view_factory_(new MockAnnotationViewFactory()),
view_manager_(std::make_unique<a11y::SemanticTreeServiceFactory>(),
std::make_unique<MockViewSemanticsFactory>(),
std::unique_ptr<MockAnnotationViewFactory>(mock_annotation_view_factory_),
context_provider_.context(), context_->outgoing()->debug_dir()),
tts_manager_(context_),
color_transform_manager_(context_),
app_(context_, &view_manager_, &tts_manager_, &color_transform_manager_,
&gesture_listener_registry_) {}
void SetUp() override {
TestLoopFixture::SetUp();
RunLoopUntilIdle();
// App is created, but is not fully-initialized. Make sure the fetch of settings only happens
// after it has been initialized.
EXPECT_EQ(0, mock_setui_.num_watch2_called());
// Right now, obtaining the locale causes the app to be fully-initialized.
ASSERT_EQ(1, mock_property_provider_.get_profile_count());
mock_property_provider_.SetLocale("en");
mock_property_provider_.ReplyToGetProfile();
RunLoopUntilIdle();
ASSERT_EQ(1,
mock_property_provider_.get_profile_count()); // Stil 1, no changes in profile yet.
// Note: 2 here because as soon as we get a settings, we call Watch2() again.
ASSERT_EQ(2, mock_setui_.num_watch2_called());
zx::eventpair::create(0u, &eventpair_, &eventpair_peer_);
view_ref_ = fuchsia::ui::views::ViewRef({
.reference = std::move(eventpair_),
});
RunLoopUntilIdle();
}
// Sends pointer events and returns the |handled| argument of the (last) resulting
// |OnStreamHandled| invocation.
//
// Yo dawg, I heard you like pointer event listener pointers, so I took a pointer to your pointer
// event listener pointer so you can receive events while you receive events (while honoring the
// C++ style guide).
std::optional<EventHandling> SendPointerEvents(PointerEventListenerPtr* listener,
const std::vector<PointerParams>& events) {
std::optional<EventHandling> event_handling;
listener->events().OnStreamHandled = [&event_handling](uint32_t /*unused*/, uint32_t /*unused*/,
EventHandling handled) {
event_handling = handled;
};
for (const auto& params : events) {
SendPointerEvent(listener->get(), params);
}
return event_handling;
}
void SendPointerEvent(PointerEventListener* listener, const PointerParams& params) {
listener->OnEvent(ToPointerEvent(params, input_event_time_++, a11y::GetKoid(view_ref_)));
// Simulate trivial passage of time (can expose edge cases with posted async tasks).
RunLoopUntilIdle();
}
// Sends a gesture that wouldn't be recognized by any accessibility feature, for testing arena
// configuration.
std::optional<EventHandling> SendUnrecognizedGesture(PointerEventListenerPtr* listener) {
return SendPointerEvents(listener, Zip({TapEvents(1, {}), TapEvents(2, {})}));
}
sys::testing::ComponentContextProvider context_provider_;
sys::ComponentContext* context_;
MockPointerEventRegistry mock_pointer_event_registry_;
MockColorTransformHandler mock_color_transform_handler_;
MockSetUIAccessibility mock_setui_;
MockFocusChain mock_focus_chain_;
MockPropertyProvider mock_property_provider_;
MockAnnotationViewFactory* mock_annotation_view_factory_;
a11y::ViewManager view_manager_;
a11y::TtsManager tts_manager_;
a11y::ColorTransformManager color_transform_manager_;
a11y::GestureListenerRegistry gesture_listener_registry_;
// App under test
a11y_manager::App app_;
fuchsia::ui::views::ViewRef view_ref_;
zx::eventpair eventpair_, eventpair_peer_;
private:
// We don't actually use these times. If we did, we'd want to more closely correlate them with
// fake time.
uint64_t input_event_time_ = 0;
};
// Test to make sure ViewManager Service is exposed by A11y.
// Test sends a node update to ViewManager and then compare the expected
// result using log file created by semantics manager.
TEST_F(AppUnitTest, UpdateNodeToSemanticsManager) {
// Create ViewRef.
fuchsia::ui::views::ViewRef view_ref_connection;
fidl::Clone(view_ref_, &view_ref_connection);
// Turn on the screen reader.
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
mock_setui_.Set(std::move(settings), [](auto) {});
// Create ActionListener.
accessibility_test::MockSemanticListener semantic_listener(&context_provider_,
std::move(view_ref_connection));
// We make sure the Semantic Listener has finished connecting to the
// root.
RunLoopUntilIdle();
// Creating test node to update.
std::vector<Node> update_nodes;
Node node = CreateTestNode(0, "Label A");
Node clone_node;
node.Clone(&clone_node);
update_nodes.push_back(std::move(clone_node));
// Update the node created above.
semantic_listener.UpdateSemanticNodes(std::move(update_nodes));
RunLoopUntilIdle();
// Commit nodes.
semantic_listener.CommitUpdates();
RunLoopUntilIdle();
// Check that the node is in the semantic tree
auto created_node = view_manager_.GetSemanticNode(a11y::GetKoid(view_ref_), 0u);
EXPECT_TRUE(created_node);
EXPECT_EQ(created_node->attributes().label(), "Label A");
// Check that the committed node is present in the logs
vfs::PseudoDir* debug_dir = context_->outgoing()->debug_dir();
vfs::internal::Node* test_node;
EXPECT_EQ(ZX_OK, debug_dir->Lookup(std::to_string(a11y::GetKoid(view_ref_)), &test_node));
}
// This test makes sure that services implemented by the Tts manager are
// available.
TEST_F(AppUnitTest, OffersTtsManagerServices) {
fuchsia::accessibility::tts::TtsManagerPtr tts_manager;
context_provider_.ConnectToPublicService(tts_manager.NewRequest());
RunLoopUntilIdle();
EXPECT_TRUE(tts_manager.is_bound());
}
TEST_F(AppUnitTest, NoListenerInitially) {
mock_setui_.Set({}, [](auto) {});
RunLoopUntilIdle();
EXPECT_FALSE(mock_pointer_event_registry_.listener())
<< "No listener should be registered in the beginning, as there is no accessibility service "
"enabled.";
}
TEST_F(AppUnitTest, ListenerForScreenReader) {
EXPECT_FALSE(app_.state().screen_reader_enabled());
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
mock_setui_.Set(std::move(settings), [](auto) {});
RunLoopUntilIdle();
EXPECT_TRUE(app_.state().screen_reader_enabled());
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::CONSUMED);
}
TEST_F(AppUnitTest, ListenerForMagnifier) {
fuchsia::settings::AccessibilitySettings settings;
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
RunLoopUntilIdle();
EXPECT_TRUE(app_.state().magnifier_enabled());
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::REJECTED);
}
TEST_F(AppUnitTest, ListenerForAll) {
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
RunLoopUntilIdle();
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::CONSUMED);
}
TEST_F(AppUnitTest, NoListenerAfterAllRemoved) {
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(false);
settings.set_enable_magnification(false);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
EXPECT_FALSE(mock_pointer_event_registry_.listener());
}
// Covers a couple additional edge cases around removing listeners.
TEST_F(AppUnitTest, ListenerRemoveOneByOne) {
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(false);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
EXPECT_EQ(app_.state().screen_reader_enabled(), false);
EXPECT_EQ(app_.state().magnifier_enabled(), true);
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::REJECTED);
{
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(false);
settings.set_enable_magnification(false);
mock_setui_.Set(std::move(settings), [](auto) {});
}
RunLoopUntilIdle();
EXPECT_EQ(app_.state().magnifier_enabled(), false);
EXPECT_FALSE(mock_pointer_event_registry_.listener());
}
// Makes sure gesture priorities are right. If they're not, screen reader would intercept this
// gesture.
TEST_F(AppUnitTest, MagnifierGestureWithScreenReader) {
MockMagnificationHandler mag_handler;
fidl::Binding<fuchsia::accessibility::MagnificationHandler> mag_handler_binding(&mag_handler);
{
fuchsia::accessibility::MagnifierPtr magnifier;
context_provider_.ConnectToPublicService(magnifier.NewRequest());
magnifier->RegisterHandler(mag_handler_binding.NewBinding());
}
fuchsia::settings::AccessibilitySettings settings;
settings.set_screen_reader(true);
settings.set_enable_magnification(true);
mock_setui_.Set(std::move(settings), [](auto) {});
RunLoopUntilIdle();
SendPointerEvents(&mock_pointer_event_registry_.listener(), 3 * TapEvents(1, {}));
RunLoopFor(a11y::Magnifier::kTransitionPeriod);
EXPECT_GT(mag_handler.transform().scale, 1);
}
TEST_F(AppUnitTest, ColorCorrectionApplied) {
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(false);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
EXPECT_EQ(fuchsia::accessibility::ColorCorrectionMode::DISABLED,
mock_color_transform_handler_.GetColorCorrectionMode());
// Turn on color correction.
fuchsia::settings::AccessibilitySettings newAccessibilitySettings;
newAccessibilitySettings.set_color_correction(
fuchsia::settings::ColorBlindnessType::DEUTERANOMALY);
mock_setui_.Set(std::move(newAccessibilitySettings), [](auto) {});
RunLoopUntilIdle();
// Verify that stuff changed
EXPECT_EQ(fuchsia::accessibility::ColorCorrectionMode::CORRECT_DEUTERANOMALY,
mock_color_transform_handler_.GetColorCorrectionMode());
}
TEST_F(AppUnitTest, ColorInversionApplied) {
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(false);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
EXPECT_FALSE(mock_color_transform_handler_.GetColorInversionEnabled());
// Turn on color correction.
fuchsia::settings::AccessibilitySettings newAccessibilitySettings;
newAccessibilitySettings.set_color_inversion(true);
mock_setui_.Set(std::move(newAccessibilitySettings), [](auto) {});
RunLoopUntilIdle();
// Verify that stuff changed
EXPECT_TRUE(mock_color_transform_handler_.GetColorInversionEnabled());
}
TEST_F(AppUnitTest, ScreenReaderOnAtStartup) {
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(true);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
// Verify that screen reader is on and the pointer event registry is wired up.
EXPECT_TRUE(app_.state().screen_reader_enabled());
ASSERT_TRUE(mock_pointer_event_registry_.listener());
EXPECT_EQ(SendUnrecognizedGesture(&mock_pointer_event_registry_.listener()),
EventHandling::CONSUMED);
}
TEST_F(AppUnitTest, InitializesFocusChain) {
// Ensures that when App is initialized, it connects to the Focus Chain different services.
RunLoopUntilIdle();
ASSERT_TRUE(mock_focus_chain_.listener());
ASSERT_TRUE(mock_focus_chain_.HasRegisteredFocuser());
}
// Makes sure FocusChain is wired up with the screen reader, when screen reader is enabled.
// This test uses explore action to make sure when a node is tapped, then screen reader can call
// RequestFocus() on FocusChain. This confirms that FocusChain is connected to ScreenReader.
TEST_F(AppUnitTest, FocusChainIsWiredToScreenReader) {
// Enable Screen Reader.
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(true);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
// Create ViewRef.
fuchsia::ui::views::ViewRef view_ref_connection;
fidl::Clone(view_ref_, &view_ref_connection);
// Create ActionListener.
accessibility_test::MockSemanticListener semantic_listener(&context_provider_,
std::move(view_ref_connection));
// We make sure the Semantic Listener has finished connecting to the
// root.
RunLoopUntilIdle();
// Creating test node to update.
std::vector<Node> update_nodes;
uint32_t node_id = 0;
std::string node_label = "Label A";
Node node = CreateTestNode(node_id, node_label);
update_nodes.push_back(std::move(node));
// Update the node created above.
semantic_listener.UpdateSemanticNodes(std::move(update_nodes));
RunLoopUntilIdle();
// Commit nodes.
semantic_listener.CommitUpdates();
RunLoopUntilIdle();
// Set HitTest result which is required to know which node is being tapped.
semantic_listener.SetHitTestResult(node_id);
// Send Tap event for view_ref_. This should trigger explore action, which should then call
// FocusChain to set focus to the tapped view.
SendPointerEvents(&mock_pointer_event_registry_.listener(), TapEvents(1, {}));
RunLoopFor(a11y::OneFingerNTapRecognizer::kTapTimeout);
ASSERT_TRUE(mock_focus_chain_.IsRequestFocusCalled());
EXPECT_EQ(a11y::GetKoid(view_ref_), mock_focus_chain_.GetFocusedViewKoid());
auto highlighted_view =
mock_annotation_view_factory_->GetAnnotationView(a11y::GetKoid(view_ref_));
EXPECT_TRUE(highlighted_view);
auto highlight = highlighted_view->GetCurrentHighlight();
EXPECT_TRUE(highlight.has_value());
}
TEST_F(AppUnitTest, FetchesLocaleInfoOnStartup) {
// App is initialized, so it should have requested once the locale.
ASSERT_EQ(1, mock_property_provider_.get_profile_count());
mock_property_provider_.SetLocale("en-US");
mock_property_provider_.SendOnChangeEvent();
RunLoopUntilIdle();
// The event causes GetProfile() to be invoked again from the a11y manager side. Check if the call
// happened through the mock.
ASSERT_EQ(2, mock_property_provider_.get_profile_count());
}
TEST_F(AppUnitTest, ScreenReaderReceivesLocaleWhenItChanges) {
fuchsia::settings::AccessibilitySettings accessibilitySettings;
accessibilitySettings.set_screen_reader(true);
accessibilitySettings.set_color_inversion(false);
accessibilitySettings.set_enable_magnification(false);
accessibilitySettings.set_color_correction(fuchsia::settings::ColorBlindnessType::NONE);
mock_setui_.Set(std::move(accessibilitySettings), [](auto) {});
RunLoopUntilIdle();
EXPECT_TRUE(app_.state().screen_reader_enabled());
EXPECT_EQ(app_.screen_reader()->context()->locale_id(), "en");
mock_property_provider_.SetLocale("en-US");
mock_property_provider_.SendOnChangeEvent();
RunLoopUntilIdle();
// The event causes GetProfile() to be invoked again from the a11y manager side. Check if the call
// happened through the mock.
ASSERT_EQ(2, mock_property_provider_.get_profile_count());
// Sends a reply.
mock_property_provider_.ReplyToGetProfile();
RunLoopUntilIdle();
EXPECT_EQ(app_.screen_reader()->context()->locale_id(), "en-US");
}
// TODO(fxb/49924): Improve tests to cover what happens if services aren't available at
// startup.
} // namespace
} // namespace accessibility_test