[Linux] add FlViewAccessible tests (flutter/engine#32769)

* [Linux] make FlViewAccessible testable

Add construct-only 'engine' property to make it possible to inject a
mock engine.
This commit is contained in:
J-P Nurmi 2022-05-10 23:16:10 +02:00 committed by GitHub
parent 8dd399a0ea
commit 8a485f85fd
4 changed files with 218 additions and 2 deletions

View File

@ -2188,6 +2188,7 @@ FILE: ../../../flutter/shell/platform/linux/fl_value_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_view.cc
FILE: ../../../flutter/shell/platform/linux/fl_view_accessible.cc
FILE: ../../../flutter/shell/platform/linux/fl_view_accessible.h
FILE: ../../../flutter/shell/platform/linux/fl_view_accessible_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_view_private.h
FILE: ../../../flutter/shell/platform/linux/key_mapping.cc
FILE: ../../../flutter/shell/platform/linux/key_mapping.h

View File

@ -214,6 +214,7 @@ executable("flutter_linux_unittests") {
"fl_texture_gl_test.cc",
"fl_texture_registrar_test.cc",
"fl_value_test.cc",
"fl_view_accessible_test.cc",
"testing/fl_test.cc",
"testing/mock_binary_messenger.cc",
"testing/mock_binary_messenger_response_handle.cc",

View File

@ -9,14 +9,33 @@
struct _FlViewAccessible {
GtkContainerAccessible parent_instance;
FlEngine* engine;
// Semantics nodes keyed by ID
GHashTable* semantics_nodes_by_id;
};
enum { PROP_0, PROP_ENGINE, PROP_LAST };
G_DEFINE_TYPE(FlViewAccessible,
fl_view_accessible,
GTK_TYPE_CONTAINER_ACCESSIBLE)
static void init_engine(FlViewAccessible* self, FlEngine* engine) {
g_assert(self->engine == nullptr);
self->engine = engine;
g_object_add_weak_pointer(G_OBJECT(self),
reinterpret_cast<gpointer*>(&self->engine));
}
static FlEngine* get_engine(FlViewAccessible* self) {
if (self->engine == nullptr) {
FlView* view = FL_VIEW(gtk_accessible_get_widget(GTK_ACCESSIBLE(self)));
init_engine(self, fl_view_get_engine(view));
}
return self->engine;
}
// Gets the ATK node for the given id.
// If the node doesn't exist it will be created.
static FlAccessibleNode* get_node(FlViewAccessible* self, int32_t id) {
@ -26,8 +45,8 @@ static FlAccessibleNode* get_node(FlViewAccessible* self, int32_t id) {
return node;
}
FlView* view = FL_VIEW(gtk_accessible_get_widget(GTK_ACCESSIBLE(self)));
node = fl_accessible_node_new(fl_view_get_engine(view), id);
FlEngine* engine = get_engine(self);
node = fl_accessible_node_new(engine, id);
if (id == 0) {
fl_accessible_node_set_parent(node, ATK_OBJECT(self), 0);
}
@ -59,10 +78,48 @@ static AtkRole fl_view_accessible_get_role(AtkObject* accessible) {
return ATK_ROLE_PANEL;
}
// Implements GObject::set_property
static void fl_view_accessible_set_property(GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec) {
FlViewAccessible* self = FL_VIEW_ACCESSIBLE(object);
switch (prop_id) {
case PROP_ENGINE:
init_engine(self, FL_ENGINE(g_value_get_object(value)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void fl_view_accessible_dispose(GObject* object) {
FlViewAccessible* self = FL_VIEW_ACCESSIBLE(object);
if (self->engine != nullptr) {
g_object_remove_weak_pointer(object,
reinterpret_cast<gpointer*>(&self->engine));
self->engine = nullptr;
}
G_OBJECT_CLASS(fl_view_accessible_parent_class)->dispose(object);
}
static void fl_view_accessible_class_init(FlViewAccessibleClass* klass) {
ATK_OBJECT_CLASS(klass)->get_n_children = fl_view_accessible_get_n_children;
ATK_OBJECT_CLASS(klass)->ref_child = fl_view_accessible_ref_child;
ATK_OBJECT_CLASS(klass)->get_role = fl_view_accessible_get_role;
G_OBJECT_CLASS(klass)->dispose = fl_view_accessible_dispose;
G_OBJECT_CLASS(klass)->set_property = fl_view_accessible_set_property;
g_object_class_install_property(
G_OBJECT_CLASS(klass), PROP_ENGINE,
g_param_spec_object(
"engine", "engine", "Flutter engine", fl_engine_get_type(),
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
}
static void fl_view_accessible_init(FlViewAccessible* self) {

View File

@ -0,0 +1,157 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Included first as it collides with the X11 headers.
#include "gtest/gtest.h"
#include "flutter/shell/platform/linux/fl_view_accessible.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
#include "flutter/shell/platform/linux/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/mock_signal_handler.h"
TEST(FlViewAccessibleTest, BuildTree) {
g_autoptr(FlEngine) engine = make_mock_engine();
g_autoptr(FlViewAccessible) accessible = FL_VIEW_ACCESSIBLE(
g_object_new(fl_view_accessible_get_type(), "engine", engine, nullptr));
const int32_t children[] = {111, 222};
const FlutterSemanticsNode root_node = {
.id = 0,
.label = "root",
.child_count = 2,
.children_in_traversal_order = children,
};
fl_view_accessible_handle_update_semantics_node(accessible, &root_node);
const FlutterSemanticsNode child1_node = {.id = 111, .label = "child 1"};
fl_view_accessible_handle_update_semantics_node(accessible, &child1_node);
const FlutterSemanticsNode child2_node = {.id = 222, .label = "child 2"};
fl_view_accessible_handle_update_semantics_node(accessible, &child2_node);
AtkObject* root_object =
atk_object_ref_accessible_child(ATK_OBJECT(accessible), 0);
EXPECT_STREQ(atk_object_get_name(root_object), "root");
EXPECT_EQ(atk_object_get_index_in_parent(root_object), 0);
EXPECT_EQ(atk_object_get_n_accessible_children(root_object), 2);
AtkObject* child1_object = atk_object_ref_accessible_child(root_object, 0);
EXPECT_STREQ(atk_object_get_name(child1_object), "child 1");
EXPECT_EQ(atk_object_get_parent(child1_object), root_object);
EXPECT_EQ(atk_object_get_index_in_parent(child1_object), 0);
EXPECT_EQ(atk_object_get_n_accessible_children(child1_object), 0);
AtkObject* child2_object = atk_object_ref_accessible_child(root_object, 1);
EXPECT_STREQ(atk_object_get_name(child2_object), "child 2");
EXPECT_EQ(atk_object_get_parent(child2_object), root_object);
EXPECT_EQ(atk_object_get_index_in_parent(child2_object), 1);
EXPECT_EQ(atk_object_get_n_accessible_children(child2_object), 0);
}
TEST(FlViewAccessibleTest, AddRemoveChildren) {
g_autoptr(FlEngine) engine = make_mock_engine();
g_autoptr(FlViewAccessible) accessible = FL_VIEW_ACCESSIBLE(
g_object_new(fl_view_accessible_get_type(), "engine", engine, nullptr));
FlutterSemanticsNode root_node = {
.id = 0,
.label = "root",
.child_count = 0,
};
fl_view_accessible_handle_update_semantics_node(accessible, &root_node);
AtkObject* root_object =
atk_object_ref_accessible_child(ATK_OBJECT(accessible), 0);
EXPECT_EQ(atk_object_get_n_accessible_children(root_object), 0);
// add child1
AtkObject* child1_object = nullptr;
{
flutter::testing::MockSignalHandler2<gint, AtkObject*> child1_added(
root_object, "children-changed::add");
EXPECT_SIGNAL2(child1_added, ::testing::Eq(0), ::testing::A<AtkObject*>())
.WillOnce(::testing::SaveArg<1>(&child1_object));
const int32_t children[] = {111};
root_node.child_count = 1;
root_node.children_in_traversal_order = children;
fl_view_accessible_handle_update_semantics_node(accessible, &root_node);
const FlutterSemanticsNode child1_node = {.id = 111, .label = "child 1"};
fl_view_accessible_handle_update_semantics_node(accessible, &child1_node);
}
EXPECT_EQ(atk_object_get_n_accessible_children(root_object), 1);
EXPECT_EQ(atk_object_ref_accessible_child(root_object, 0), child1_object);
EXPECT_STREQ(atk_object_get_name(child1_object), "child 1");
EXPECT_EQ(atk_object_get_parent(child1_object), root_object);
EXPECT_EQ(atk_object_get_index_in_parent(child1_object), 0);
EXPECT_EQ(atk_object_get_n_accessible_children(child1_object), 0);
// add child2
AtkObject* child2_object = nullptr;
{
flutter::testing::MockSignalHandler2<gint, AtkObject*> child2_added(
root_object, "children-changed::add");
EXPECT_SIGNAL2(child2_added, ::testing::Eq(1), ::testing::A<AtkObject*>())
.WillOnce(::testing::SaveArg<1>(&child2_object));
const int32_t children[] = {111, 222};
root_node.child_count = 2;
root_node.children_in_traversal_order = children;
fl_view_accessible_handle_update_semantics_node(accessible, &root_node);
const FlutterSemanticsNode child2_node = {.id = 222, .label = "child 2"};
fl_view_accessible_handle_update_semantics_node(accessible, &child2_node);
}
EXPECT_EQ(atk_object_get_n_accessible_children(root_object), 2);
EXPECT_EQ(atk_object_ref_accessible_child(root_object, 0), child1_object);
EXPECT_EQ(atk_object_ref_accessible_child(root_object, 1), child2_object);
EXPECT_STREQ(atk_object_get_name(child1_object), "child 1");
EXPECT_EQ(atk_object_get_parent(child1_object), root_object);
EXPECT_EQ(atk_object_get_index_in_parent(child1_object), 0);
EXPECT_EQ(atk_object_get_n_accessible_children(child1_object), 0);
EXPECT_STREQ(atk_object_get_name(child2_object), "child 2");
EXPECT_EQ(atk_object_get_parent(child2_object), root_object);
EXPECT_EQ(atk_object_get_index_in_parent(child2_object), 1);
EXPECT_EQ(atk_object_get_n_accessible_children(child2_object), 0);
// remove child1
{
flutter::testing::MockSignalHandler2<gint, AtkObject*> child1_removed(
root_object, "children-changed::remove");
EXPECT_SIGNAL2(child1_removed, ::testing::Eq(0),
::testing::Eq(child1_object));
const int32_t children[] = {222};
root_node.child_count = 1;
root_node.children_in_traversal_order = children;
fl_view_accessible_handle_update_semantics_node(accessible, &root_node);
}
EXPECT_EQ(atk_object_get_n_accessible_children(root_object), 1);
EXPECT_EQ(atk_object_ref_accessible_child(root_object, 0), child2_object);
EXPECT_STREQ(atk_object_get_name(child2_object), "child 2");
EXPECT_EQ(atk_object_get_parent(child2_object), root_object);
EXPECT_EQ(atk_object_get_index_in_parent(child2_object), 0);
EXPECT_EQ(atk_object_get_n_accessible_children(child2_object), 0);
// remove child2
{
flutter::testing::MockSignalHandler2<gint, AtkObject*> child2_removed(
root_object, "children-changed::remove");
EXPECT_SIGNAL2(child2_removed, ::testing::Eq(0),
::testing::Eq(child2_object));
root_node.child_count = 0;
fl_view_accessible_handle_update_semantics_node(accessible, &root_node);
}
EXPECT_EQ(atk_object_get_n_accessible_children(root_object), 0);
}