blob: cf893b249eed5ab97f352deb511250d2db332cae [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 <lib/syslog/cpp/macros.h>
#include <gtest/gtest.h>
#include "src/lib/fsl/handles/object_info.h"
#include "src/lib/ui/base_view/embedded_view_utils.h"
#include "src/ui/a11y/lib/semantics/tests/semantics_integration_test_fixture.h"
#include "src/ui/testing/views/embedder_view.h"
namespace accessibility_test {
namespace {
constexpr char kClientUrl[] = "fuchsia-pkg://fuchsia.com/a11y-demo#meta/a11y-demo.cmx";
constexpr zx::duration kTimeout = zx::sec(15);
class FlutterSemanticsTests : public SemanticsIntegrationTest {
public:
FlutterSemanticsTests() : SemanticsIntegrationTest("flutter_semantics_test") {}
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SemanticsIntegrationTest::SetUp());
view_manager()->SetSemanticsEnabled(true);
scenic::EmbeddedViewInfo flutter_runner =
scenic::LaunchComponentAndCreateView(environment()->launcher_ptr(), kClientUrl);
flutter_runner.controller.events().OnTerminated = [](auto...) { FAIL(); };
view_ref_koid_ = fsl::GetKoid(flutter_runner.view_ref.reference.get());
// Present the view.
embedder_view_.emplace(scenic::ViewContext{
.session_and_listener_request = scenic::CreateScenicSessionPtrAndListenerRequest(scenic()),
.view_token = CreatePresentationViewToken(),
});
// Embed the view.
bool is_rendering = false;
embedder_view_->EmbedView(std::move(flutter_runner),
[&is_rendering](fuchsia::ui::gfx::ViewState view_state) {
is_rendering = view_state.is_rendering;
});
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&is_rendering] { return is_rendering; }, kTimeout));
ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
[&] {
auto node = view_manager()->GetSemanticNode(view_ref_koid_, 0u);
return node != nullptr && node->has_attributes() && node->attributes().has_label();
},
kTimeout))
<< "No root node found.";
// a11y-demo's semantic tree:
// ID: 0 Label:
// ID: 1 Label:Blue tapped 0 times
// ID: 2 Label:Yellow tapped 0 times
// ID: 3 Label:
// ID: 6 Label:
// ID: 4 Label:Blue
// ID: 5 Label:Yellow
}
zx_koid_t view_ref_koid() const { return view_ref_koid_; }
private:
// Wrapped in optional since the view is not created until the middle of SetUp
std::optional<scenic::EmbedderView> embedder_view_;
zx_koid_t view_ref_koid_;
};
// Loads ally-demo flutter app and verifies its semantic tree.
TEST_F(FlutterSemanticsTests, StaticSemantics) {
auto root = view_manager()->GetSemanticNode(view_ref_koid(), 0u);
auto node = FindNodeWithLabel(root, view_ref_koid(), "Blue tapped 0 times");
ASSERT_TRUE(node);
node = FindNodeWithLabel(root, view_ref_koid(), "Yellow tapped 0 times");
ASSERT_TRUE(node);
node = FindNodeWithLabel(root, view_ref_koid(), "Blue");
ASSERT_TRUE(node);
node = FindNodeWithLabel(root, view_ref_koid(), "Yellow");
ASSERT_TRUE(node);
}
// Loads ally-demo flutter app and validates hit testing
TEST_F(FlutterSemanticsTests, HitTesting) {
auto root = view_manager()->GetSemanticNode(view_ref_koid(), 0u);
// We'll target all hits just within the bounding box of the nodes.
fuchsia::math::PointF offset{1., 1.};
// Hit test something with an action
auto node = FindNodeWithLabel(root, view_ref_koid(), "Blue");
ASSERT_TRUE(node);
auto hit_node = HitTest(view_ref_koid(), CalculateViewTargetPoint(view_ref_koid(), node, offset));
ASSERT_TRUE(hit_node.has_value());
ASSERT_EQ(*hit_node, node->node_id());
// Hit test a label
node = FindNodeWithLabel(root, view_ref_koid(), "Yellow tapped 0 times");
ASSERT_TRUE(node);
hit_node = HitTest(view_ref_koid(), CalculateViewTargetPoint(view_ref_koid(), node, offset));
ASSERT_TRUE(hit_node.has_value());
ASSERT_EQ(*hit_node, node->node_id());
}
// Loads ally-demo flutter app and validates triggering actions
TEST_F(FlutterSemanticsTests, PerformAction) {
auto root = view_manager()->GetSemanticNode(view_ref_koid(), 0u);
// Verify the counter is currently at 0
auto node = FindNodeWithLabel(root, view_ref_koid(), "Blue tapped 0 times");
EXPECT_TRUE(node);
// Trigger the button's default action
node = FindNodeWithLabel(root, view_ref_koid(), "Blue");
ASSERT_TRUE(node);
bool callback_handled = PerformAccessibilityAction(
view_ref_koid(), node->node_id(), fuchsia::accessibility::semantics::Action::DEFAULT);
EXPECT_TRUE(callback_handled);
// Verify the counter is now at 1
// TODO(fxb.dev/58276): Once we have the Semantic Event Updates work done, this logic can be
// more clearly written as waiting for notification of an update then checking the tree.
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[this, root] {
auto node = FindNodeWithLabel(root, view_ref_koid(), "Blue tapped 1 time");
return node != nullptr;
},
kTimeout));
}
// Loads ally-demo flutter app and validates scroll-to-make-visible
TEST_F(FlutterSemanticsTests, ScrollToMakeVisible) {
auto root = view_manager()->GetSemanticNode(view_ref_koid(), 0u);
// The "Yellow" node should be off-screen in a scrollable list
auto node = FindNodeWithLabel(root, view_ref_koid(), "Yellow");
ASSERT_TRUE(node);
// Record the location of a corner of the node's bounding box. We record this rather than the
// transform or the location fields since the runtime could change either when an element is
// moved.
auto node_corner =
GetTransformForNode(view_ref_koid(), node->node_id()).Apply(node->location().min);
bool callback_handled = PerformAccessibilityAction(
view_ref_koid(), node->node_id(), fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
EXPECT_TRUE(callback_handled);
// Verify the "Yellow" node has moved
// TODO(fxb.dev/58276): Once we have the Semantic Event Updates work done, this logic can be
// more clearly written as waiting for notification of an update then checking the tree.
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[this, root, &node_corner] {
auto node = FindNodeWithLabel(root, view_ref_koid(), "Yellow");
if (node == nullptr) {
return false;
}
auto new_node_corner =
GetTransformForNode(view_ref_koid(), node->node_id()).Apply(node->location().min);
return node_corner.x != new_node_corner.x || node_corner.y != new_node_corner.y ||
node_corner.z != new_node_corner.z;
},
kTimeout));
}
} // namespace
} // namespace accessibility_test