blob: 55d495f7fe86def44cf5a6938294f3395a86b81c [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/a11y/lib/semantics/tests/semantics_integration_test_fixture.h"
#include <fuchsia/ui/input/accessibility/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <lib/fdio/spawn.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include <stack>
#include <vector>
#include "src/ui/a11y/lib/annotation/tests/mocks/mock_annotation_view.h"
#include "src/ui/a11y/lib/semantics/a11y_semantics_event_manager.h"
#include "src/ui/a11y/lib/view/tests/mocks/mock_view_semantics.h"
namespace accessibility_test {
namespace {
constexpr zx::duration kTimeout = zx::sec(60);
} // namespace
SemanticsIntegrationTest::SemanticsIntegrationTest(const std::string& environment_label)
: environment_label_(environment_label),
component_context_(sys::ComponentContext::CreateAndServeOutgoingDirectory()),
view_manager_(std::make_unique<a11y::SemanticTreeServiceFactory>(),
std::make_unique<MockViewSemanticsFactory>(),
std::make_unique<MockAnnotationViewFactory>(),
std::make_unique<a11y::A11ySemanticsEventManager>(), component_context_.get(),
component_context_->outgoing()->debug_dir()),
scenic_(component_context_->svc()->Connect<fuchsia::ui::scenic::Scenic>()) {
scenic_.set_error_handler([](zx_status_t status) {
FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status);
});
// Wait for scenic to get initialized by calling GetDisplayInfo.
scenic_->GetDisplayInfo([this](fuchsia::ui::gfx::DisplayInfo info) { QuitLoop(); });
RunLoopWithTimeout(kTimeout);
}
void SemanticsIntegrationTest::SetUp() {
TestWithEnvironment::SetUp();
// This is done in |SetUp| as opposed to the constructor to allow subclasses the opportunity to
// override |CreateServices()|.
auto services = TestWithEnvironment::CreateServices();
services->AddService(semantics_manager_bindings_.GetHandler(&view_manager_));
CreateServices(services);
environment_ = CreateNewEnclosingEnvironment(environment_label_, std::move(services),
{.inherit_parent_services = true});
}
fuchsia::ui::views::ViewToken SemanticsIntegrationTest::CreatePresentationViewToken() {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto presenter = real_services()->Connect<fuchsia::ui::policy::Presenter>();
presenter.set_error_handler(
[](zx_status_t status) { FAIL() << "presenter: " << zx_status_get_string(status); });
presenter->PresentView(std::move(view_holder_token), nullptr);
return std::move(view_token);
}
const Node* SemanticsIntegrationTest::FindNodeWithLabel(const Node* node, zx_koid_t view_ref_koid,
std::string label) {
if (!node) {
return nullptr;
}
if (node->has_attributes() && node->attributes().has_label() &&
node->attributes().label() == label) {
return node;
}
if (!node->has_child_ids()) {
return nullptr;
}
for (const auto& child_id : node->child_ids()) {
const auto* child = view_manager()->GetSemanticNode(view_ref_koid, child_id);
FX_DCHECK(child);
auto result = FindNodeWithLabel(child, view_ref_koid, label);
if (result != nullptr) {
return result;
}
}
return nullptr;
}
a11y::SemanticTransform SemanticsIntegrationTest::GetTransformForNode(zx_koid_t view_ref_koid,
uint32_t node_id) {
std::stack<const Node*> path;
// Perform a DFS to find the path to the target node
std::function<bool(const Node*)> traverse = [&](const Node* node) {
if (node->node_id() == node_id) {
path.push(node);
return true;
}
if (!node->has_child_ids()) {
return false;
}
for (const auto& child_id : node->child_ids()) {
const auto* child = view_manager()->GetSemanticNode(view_ref_koid, child_id);
FX_DCHECK(child);
if (traverse(child)) {
path.push(node);
return true;
}
}
return false;
};
auto root = view_manager()->GetSemanticNode(view_ref_koid, 0u);
traverse(root);
a11y::SemanticTransform transform;
while (!path.empty()) {
auto node = path.top();
if (node->has_transform()) {
transform.ChainLocalTransform(node->transform());
}
path.pop();
}
return transform;
}
std::optional<uint32_t> SemanticsIntegrationTest::HitTest(zx_koid_t view_ref_koid,
fuchsia::math::PointF target) {
std::optional<fuchsia::accessibility::semantics::Hit> target_hit;
auto hit_callback = [&](fuchsia::accessibility::semantics::Hit hit) {
target_hit = std::move(hit);
};
view_manager()->ExecuteHitTesting(view_ref_koid, target, hit_callback);
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return target_hit.has_value(); }, kTimeout));
if (!target_hit.has_value() || !target_hit->has_node_id()) {
return std::nullopt;
}
return target_hit->node_id();
}
fuchsia::math::PointF SemanticsIntegrationTest::CalculateViewTargetPoint(
zx_koid_t view_ref_koid, const fuchsia::accessibility::semantics::Node* node,
fuchsia::math::PointF offset) {
// Semantic trees may have transforms in each node. That transform defines the spatial relation
// between coordinates in the node's space to coordinates in it's parent's space. This is done
// to enable semantic providers to avoid recomputing location information on every child node
// when a parent node (or the entire view) undergoes a spatial change.
// Get the transform from the node's local space to the view's local space.
auto transform = GetTransformForNode(view_ref_koid, node->node_id());
// Calculate the point within the node's local space we want to target
fuchsia::ui::gfx::vec3 node_local_target_point = {
node->location().min.x + offset.x,
node->location().min.y + offset.y,
node->location().min.z,
};
// Transform that point into the view's local space.
auto view_local_target_point = transform.Apply(node_local_target_point);
return {view_local_target_point.x, view_local_target_point.y};
}
bool SemanticsIntegrationTest::PerformAccessibilityAction(
zx_koid_t view_ref_koid, uint32_t node_id, fuchsia::accessibility::semantics::Action action) {
std::optional<bool> callback_handled;
auto callback = [&callback_handled](bool handled) { callback_handled = handled; };
view_manager()->PerformAccessibilityAction(view_ref_koid, node_id, action, callback);
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[&callback_handled] { return callback_handled.has_value(); }, kTimeout));
if (!callback_handled.has_value()) {
// The EXPECT above will have failed, so this will show up as a test failure
return false;
}
return *callback_handled;
}
} // namespace accessibility_test