| // Copyright 2017 The Chromium 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 <atk/atk.h> |
| #include <dlfcn.h> |
| #include <utility> |
| #include <vector> |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/accessibility/platform/ax_platform_node_auralinux.h" |
| #include "ui/accessibility/platform/ax_platform_node_unittest.h" |
| #include "ui/accessibility/platform/test_ax_node_wrapper.h" |
| |
| namespace ui { |
| |
| class AXPlatformNodeAuraLinuxTest : public AXPlatformNodeTest { |
| public: |
| AXPlatformNodeAuraLinuxTest() {} |
| ~AXPlatformNodeAuraLinuxTest() override {} |
| |
| void SetUp() override {} |
| |
| protected: |
| AXPlatformNodeAuraLinux* GetPlatformNode(AXNode* node) { |
| TestAXNodeWrapper* wrapper = |
| TestAXNodeWrapper::GetOrCreate(tree_.get(), node); |
| if (!wrapper) |
| return nullptr; |
| return static_cast<AXPlatformNodeAuraLinux*>(wrapper->ax_platform_node()); |
| } |
| |
| AXPlatformNodeAuraLinux* GetRootPlatformNode() { |
| return GetPlatformNode(GetRootNode()); |
| } |
| |
| AtkObject* AtkObjectFromNode(AXNode* node) { |
| if (AXPlatformNode* ax_platform_node = GetPlatformNode(node)) { |
| return ax_platform_node->GetNativeViewAccessible(); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| TestAXNodeWrapper* GetRootWrapper() { |
| return TestAXNodeWrapper::GetOrCreate(tree_.get(), GetRootNode()); |
| } |
| |
| AtkObject* GetRootAtkObject() { return AtkObjectFromNode(GetRootNode()); } |
| }; |
| |
| static void EnsureAtkObjectHasAttributeWithValue( |
| AtkObject* atk_object, |
| const gchar* attribute_name, |
| const gchar* attribute_value) { |
| AtkAttributeSet* attributes = atk_object_get_attributes(atk_object); |
| bool saw_attribute = false; |
| |
| AtkAttributeSet* current = attributes; |
| while (current) { |
| AtkAttribute* attribute = static_cast<AtkAttribute*>(current->data); |
| |
| if (0 == strcmp(attribute_name, attribute->name)) { |
| // Ensure that we only see this attribute once. |
| ASSERT_FALSE(saw_attribute) << attribute_name; |
| |
| EXPECT_STREQ(attribute_value, attribute->value); |
| saw_attribute = true; |
| } |
| |
| current = current->next; |
| } |
| |
| ASSERT_TRUE(saw_attribute); |
| atk_attribute_set_free(attributes); |
| } |
| |
| static void EnsureAtkObjectDoesNotHaveAttribute( |
| AtkObject* atk_object, |
| const gchar* attribute_name) { |
| AtkAttributeSet* attributes = atk_object_get_attributes(atk_object); |
| AtkAttributeSet* current = attributes; |
| while (current) { |
| AtkAttribute* attribute = static_cast<AtkAttribute*>(current->data); |
| ASSERT_STRNE(attribute_name, attribute->name) << attribute_name; |
| current = current->next; |
| } |
| atk_attribute_set_free(attributes); |
| } |
| |
| static void SetStringAttributeOnNode( |
| AXNode* ax_node, |
| ax::mojom::StringAttribute attribute, |
| const char* attribute_value, |
| base::Optional<ax::mojom::Role> role = base::nullopt) { |
| AXNodeData new_data = AXNodeData(); |
| new_data.role = role.value_or(ax::mojom::Role::kApplication); |
| new_data.id = ax_node->data().id; |
| new_data.AddStringAttribute(attribute, attribute_value); |
| ax_node->SetData(new_data); |
| } |
| |
| static void TestAtkObjectIntAttribute( |
| AXNode* ax_node, |
| AtkObject* atk_object, |
| ax::mojom::IntAttribute mojom_attribute, |
| const gchar* attribute_name, |
| base::Optional<ax::mojom::Role> role = base::nullopt) { |
| AXNodeData new_data = AXNodeData(); |
| new_data.role = role.value_or(ax::mojom::Role::kApplication); |
| ax_node->SetData(new_data); |
| EnsureAtkObjectDoesNotHaveAttribute(atk_object, attribute_name); |
| |
| std::pair<int, const char*> tests[] = { |
| std::make_pair(0, "0"), std::make_pair(1, "1"), |
| std::make_pair(2, "2"), std::make_pair(-100, "-100"), |
| std::make_pair(1000, "1000"), |
| }; |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| AXNodeData new_data = AXNodeData(); |
| new_data.role = role.value_or(ax::mojom::Role::kApplication); |
| new_data.id = ax_node->data().id; |
| new_data.AddIntAttribute(mojom_attribute, tests[i].first); |
| ax_node->SetData(new_data); |
| EnsureAtkObjectHasAttributeWithValue(atk_object, attribute_name, |
| tests[i].second); |
| } |
| } |
| |
| static void TestAtkObjectStringAttribute( |
| AXNode* ax_node, |
| AtkObject* atk_object, |
| ax::mojom::StringAttribute mojom_attribute, |
| const gchar* attribute_name, |
| base::Optional<ax::mojom::Role> role = base::nullopt) { |
| AXNodeData new_data = AXNodeData(); |
| new_data.role = role.value_or(ax::mojom::Role::kApplication); |
| ax_node->SetData(new_data); |
| EnsureAtkObjectDoesNotHaveAttribute(atk_object, attribute_name); |
| |
| const char* tests[] = { |
| "", |
| "a string with spaces" |
| "a string with , a comma", |
| "\xE2\x98\xBA", // The smiley emoji. |
| }; |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| SetStringAttributeOnNode(ax_node, mojom_attribute, tests[i], role); |
| EnsureAtkObjectHasAttributeWithValue(atk_object, attribute_name, tests[i]); |
| } |
| } |
| |
| static void TestAtkObjectBoolAttribute( |
| AXNode* ax_node, |
| AtkObject* atk_object, |
| ax::mojom::BoolAttribute mojom_attribute, |
| const gchar* attribute_name, |
| base::Optional<ax::mojom::Role> role = base::nullopt) { |
| AXNodeData new_data = AXNodeData(); |
| new_data.role = role.value_or(ax::mojom::Role::kApplication); |
| ax_node->SetData(new_data); |
| EnsureAtkObjectDoesNotHaveAttribute(atk_object, attribute_name); |
| |
| new_data = AXNodeData(); |
| new_data.role = role.value_or(ax::mojom::Role::kApplication); |
| new_data.id = ax_node->data().id; |
| new_data.AddBoolAttribute(mojom_attribute, true); |
| ax_node->SetData(new_data); |
| EnsureAtkObjectHasAttributeWithValue(atk_object, attribute_name, "true"); |
| |
| new_data = AXNodeData(); |
| new_data.role = role.value_or(ax::mojom::Role::kApplication); |
| new_data.id = ax_node->data().id; |
| new_data.AddBoolAttribute(mojom_attribute, false); |
| ax_node->SetData(new_data); |
| EnsureAtkObjectHasAttributeWithValue(atk_object, attribute_name, "false"); |
| } |
| |
| // |
| // AtkObject tests |
| // |
| #if defined(ATK_CHECK_VERSION) && ATK_CHECK_VERSION(2, 16, 0) |
| #define ATK_216 |
| #endif |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectDetachedObject) { |
| AXNodeData root; |
| root.id = 1; |
| root.SetName("Name"); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| const gchar* name = atk_object_get_name(root_obj); |
| EXPECT_STREQ("Name", name); |
| |
| AtkStateSet* state_set = atk_object_ref_state_set(root_obj); |
| ASSERT_TRUE(ATK_IS_STATE_SET(state_set)); |
| EXPECT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_DEFUNCT)); |
| g_object_unref(state_set); |
| |
| tree_.reset(new AXTree()); |
| EXPECT_EQ(nullptr, atk_object_get_name(root_obj)); |
| |
| state_set = atk_object_ref_state_set(root_obj); |
| ASSERT_TRUE(ATK_IS_STATE_SET(state_set)); |
| EXPECT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_DEFUNCT)); |
| g_object_unref(state_set); |
| |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectName) { |
| AXNodeData root; |
| root.id = 1; |
| root.SetName("Name"); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| const gchar* name = atk_object_get_name(root_obj); |
| EXPECT_STREQ("Name", name); |
| |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectDescription) { |
| AXNodeData root; |
| root.id = 1; |
| root.AddStringAttribute(ax::mojom::StringAttribute::kDescription, |
| "Description"); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| const gchar* description = atk_object_get_description(root_obj); |
| EXPECT_STREQ("Description", description); |
| |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectRole) { |
| AXNodeData root; |
| root.id = 1; |
| root.child_ids.push_back(2); |
| root.role = ax::mojom::Role::kApplication; |
| |
| AXNodeData child; |
| child.id = 2; |
| |
| Init(root, child); |
| AXNode* child_node = GetRootNode()->children()[0]; |
| |
| AtkObject* root_obj(AtkObjectFromNode(GetRootNode())); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| EXPECT_EQ(ATK_ROLE_APPLICATION, atk_object_get_role(root_obj)); |
| g_object_unref(root_obj); |
| |
| child.role = ax::mojom::Role::kAlert; |
| child_node->SetData(child); |
| AtkObject* child_obj(AtkObjectFromNode(child_node)); |
| ASSERT_TRUE(ATK_IS_OBJECT(child_obj)); |
| g_object_ref(child_obj); |
| EXPECT_EQ(ATK_ROLE_ALERT, atk_object_get_role(child_obj)); |
| g_object_unref(child_obj); |
| |
| child.role = ax::mojom::Role::kButton; |
| child_node->SetData(child); |
| child_obj = AtkObjectFromNode(child_node); |
| ASSERT_TRUE(ATK_IS_OBJECT(child_obj)); |
| g_object_ref(child_obj); |
| EXPECT_EQ(ATK_ROLE_PUSH_BUTTON, atk_object_get_role(child_obj)); |
| g_object_unref(child_obj); |
| |
| child.role = ax::mojom::Role::kCanvas; |
| child_node->SetData(child); |
| child_obj = AtkObjectFromNode(child_node); |
| ASSERT_TRUE(ATK_IS_OBJECT(child_obj)); |
| g_object_ref(child_obj); |
| EXPECT_EQ(ATK_ROLE_CANVAS, atk_object_get_role(child_obj)); |
| g_object_unref(child_obj); |
| |
| child.role = ax::mojom::Role::kApplication; |
| child_node->SetData(child); |
| child_obj = AtkObjectFromNode(child_node); |
| ASSERT_TRUE(ATK_IS_OBJECT(child_obj)); |
| g_object_ref(child_obj); |
| EXPECT_EQ(ATK_ROLE_EMBEDDED, atk_object_get_role(child_obj)); |
| g_object_unref(child_obj); |
| |
| child.role = ax::mojom::Role::kWindow; |
| child_node->SetData(child); |
| child_obj = AtkObjectFromNode(child_node); |
| ASSERT_TRUE(ATK_IS_OBJECT(child_obj)); |
| g_object_ref(child_obj); |
| EXPECT_EQ(ATK_ROLE_FRAME, atk_object_get_role(child_obj)); |
| g_object_unref(child_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectState) { |
| AXNodeData root; |
| root.id = 1; |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| AtkStateSet* state_set = atk_object_ref_state_set(root_obj); |
| ASSERT_TRUE(ATK_IS_STATE_SET(state_set)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_ENABLED)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_SENSITIVE)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_SHOWING)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_VISIBLE)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_BUSY)); |
| #if defined(ATK_216) |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_CHECKABLE)); |
| #endif |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_CHECKED)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_DEFAULT)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_EDITABLE)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_EXPANDABLE)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_EXPANDED)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_FOCUSABLE)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_FOCUSED)); |
| #if defined(ATK_216) |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_HAS_POPUP)); |
| #endif |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_HORIZONTAL)); |
| ASSERT_FALSE( |
| atk_state_set_contains_state(state_set, ATK_STATE_INVALID_ENTRY)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_MODAL)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_MULTI_LINE)); |
| ASSERT_FALSE( |
| atk_state_set_contains_state(state_set, ATK_STATE_MULTISELECTABLE)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_REQUIRED)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_SELECTABLE)); |
| ASSERT_FALSE( |
| atk_state_set_contains_state(state_set, ATK_STATE_SELECTABLE_TEXT)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_SELECTED)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_SINGLE_LINE)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, |
| ATK_STATE_SUPPORTS_AUTOCOMPLETION)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_VERTICAL)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_VISITED)); |
| g_object_unref(state_set); |
| |
| root = AXNodeData(); |
| root.AddState(ax::mojom::State::kDefault); |
| root.AddState(ax::mojom::State::kEditable); |
| root.AddState(ax::mojom::State::kExpanded); |
| root.AddState(ax::mojom::State::kFocusable); |
| root.AddState(ax::mojom::State::kMultiselectable); |
| root.AddState(ax::mojom::State::kRequired); |
| root.AddState(ax::mojom::State::kVertical); |
| root.AddBoolAttribute(ax::mojom::BoolAttribute::kBusy, true); |
| root.SetInvalidState(ax::mojom::InvalidState::kTrue); |
| root.AddStringAttribute(ax::mojom::StringAttribute::kAutoComplete, "foo"); |
| GetRootNode()->SetData(root); |
| |
| state_set = atk_object_ref_state_set(root_obj); |
| ASSERT_TRUE(ATK_IS_STATE_SET(state_set)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_BUSY)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_DEFAULT)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_EDITABLE)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_EXPANDABLE)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_EXPANDED)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_FOCUSABLE)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_INVALID_ENTRY)); |
| ASSERT_TRUE( |
| atk_state_set_contains_state(state_set, ATK_STATE_MULTISELECTABLE)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_REQUIRED)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, |
| ATK_STATE_SUPPORTS_AUTOCOMPLETION)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_VERTICAL)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_FOCUSED)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_HORIZONTAL)); |
| g_object_unref(state_set); |
| |
| root = AXNodeData(); |
| root.AddState(ax::mojom::State::kCollapsed); |
| root.AddState(ax::mojom::State::kHorizontal); |
| root.AddState(ax::mojom::State::kVisited); |
| root.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true); |
| root.SetHasPopup(ax::mojom::HasPopup::kTrue); |
| GetRootNode()->SetData(root); |
| |
| state_set = atk_object_ref_state_set(root_obj); |
| ASSERT_TRUE(ATK_IS_STATE_SET(state_set)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_EXPANDABLE)); |
| #if defined(ATK_216) |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_HAS_POPUP)); |
| #endif |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_HORIZONTAL)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_SELECTABLE)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_SELECTED)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_VISITED)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_EXPANDED)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_VERTICAL)); |
| g_object_unref(state_set); |
| |
| root = AXNodeData(); |
| root.AddState(ax::mojom::State::kInvisible); |
| root.AddBoolAttribute(ax::mojom::BoolAttribute::kModal, true); |
| GetRootNode()->SetData(root); |
| |
| state_set = atk_object_ref_state_set(root_obj); |
| ASSERT_TRUE(ATK_IS_STATE_SET(state_set)); |
| ASSERT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_MODAL)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_SHOWING)); |
| ASSERT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_VISIBLE)); |
| g_object_unref(state_set); |
| |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectChildAndParent) { |
| AXNodeData root; |
| root.id = 1; |
| root.child_ids.push_back(2); |
| root.child_ids.push_back(3); |
| |
| AXNodeData button; |
| button.role = ax::mojom::Role::kButton; |
| button.id = 2; |
| |
| AXNodeData checkbox; |
| checkbox.role = ax::mojom::Role::kCheckBox; |
| checkbox.id = 3; |
| |
| Init(root, button, checkbox); |
| AXNode* button_node = GetRootNode()->children()[0]; |
| AXNode* checkbox_node = GetRootNode()->children()[1]; |
| AtkObject* root_obj = GetRootAtkObject(); |
| AtkObject* button_obj = AtkObjectFromNode(button_node); |
| AtkObject* checkbox_obj = AtkObjectFromNode(checkbox_node); |
| |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| EXPECT_EQ(2, atk_object_get_n_accessible_children(root_obj)); |
| ASSERT_TRUE(ATK_IS_OBJECT(button_obj)); |
| EXPECT_EQ(0, atk_object_get_n_accessible_children(button_obj)); |
| ASSERT_TRUE(ATK_IS_OBJECT(checkbox_obj)); |
| EXPECT_EQ(0, atk_object_get_n_accessible_children(checkbox_obj)); |
| |
| { |
| AtkObject* result = atk_object_ref_accessible_child(root_obj, 0); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_obj)); |
| EXPECT_EQ(result, button_obj); |
| g_object_unref(result); |
| } |
| { |
| AtkObject* result = atk_object_ref_accessible_child(root_obj, 1); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_obj)); |
| EXPECT_EQ(result, checkbox_obj); |
| g_object_unref(result); |
| } |
| |
| // Now check parents. |
| { |
| AtkObject* result = atk_object_get_parent(button_obj); |
| EXPECT_TRUE(ATK_IS_OBJECT(result)); |
| EXPECT_EQ(result, root_obj); |
| } |
| { |
| AtkObject* result = atk_object_get_parent(checkbox_obj); |
| EXPECT_TRUE(ATK_IS_OBJECT(result)); |
| EXPECT_EQ(result, root_obj); |
| } |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectIndexInParent) { |
| AXNodeData root; |
| root.id = 1; |
| root.child_ids.push_back(2); |
| root.child_ids.push_back(3); |
| |
| AXNodeData left; |
| left.id = 2; |
| |
| AXNodeData right; |
| right.id = 3; |
| |
| Init(root, left, right); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| AtkObject* left_obj = atk_object_ref_accessible_child(root_obj, 0); |
| ASSERT_TRUE(ATK_IS_OBJECT(left_obj)); |
| AtkObject* right_obj = atk_object_ref_accessible_child(root_obj, 1); |
| ASSERT_TRUE(ATK_IS_OBJECT(right_obj)); |
| |
| EXPECT_EQ(0, atk_object_get_index_in_parent(left_obj)); |
| EXPECT_EQ(1, atk_object_get_index_in_parent(right_obj)); |
| |
| g_object_unref(left_obj); |
| g_object_unref(right_obj); |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectStringAttributes) { |
| AXNodeData root_data; |
| root_data.id = 1; |
| |
| Init(root_data); |
| |
| AXNode* root_node = GetRootNode(); |
| AtkObject* root_atk_object(AtkObjectFromNode(root_node)); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| std::pair<ax::mojom::StringAttribute, const char*> tests[] = { |
| std::make_pair(ax::mojom::StringAttribute::kDisplay, "display"), |
| std::make_pair(ax::mojom::StringAttribute::kHtmlTag, "tag"), |
| std::make_pair(ax::mojom::StringAttribute::kRole, "xml-roles"), |
| std::make_pair(ax::mojom::StringAttribute::kPlaceholder, "placeholder"), |
| std::make_pair(ax::mojom::StringAttribute::kRoleDescription, |
| "roledescription"), |
| std::make_pair(ax::mojom::StringAttribute::kKeyShortcuts, "keyshortcuts"), |
| std::make_pair(ax::mojom::StringAttribute::kLiveStatus, "live"), |
| std::make_pair(ax::mojom::StringAttribute::kLiveRelevant, "relevant"), |
| std::make_pair(ax::mojom::StringAttribute::kContainerLiveStatus, |
| "container-live"), |
| std::make_pair(ax::mojom::StringAttribute::kContainerLiveRelevant, |
| "container-relevant"), |
| }; |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| TestAtkObjectStringAttribute(root_node, root_atk_object, tests[i].first, |
| tests[i].second); |
| } |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectBoolAttributes) { |
| AXNodeData root_data; |
| root_data.id = 1; |
| |
| Init(root_data); |
| |
| AXNode* root_node = GetRootNode(); |
| AtkObject* root_atk_object(AtkObjectFromNode(root_node)); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| std::pair<ax::mojom::BoolAttribute, const char*> tests[] = { |
| std::make_pair(ax::mojom::BoolAttribute::kLiveAtomic, "atomic"), |
| std::make_pair(ax::mojom::BoolAttribute::kBusy, "busy"), |
| std::make_pair(ax::mojom::BoolAttribute::kContainerLiveAtomic, |
| "container-atomic"), |
| std::make_pair(ax::mojom::BoolAttribute::kContainerLiveBusy, |
| "container-busy"), |
| }; |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| TestAtkObjectBoolAttribute(root_node, root_atk_object, tests[i].first, |
| tests[i].second); |
| } |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, DISABLED_TestAtkObjectIntAttributes) { |
| AXNodeData root_data; |
| root_data.id = 1; |
| Init(root_data); |
| |
| AXNode* root_node = GetRootNode(); |
| AtkObject* root_atk_object(AtkObjectFromNode(root_node)); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kHierarchicalLevel, |
| "level"); |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kAriaColumnCount, |
| "colcount", ax::mojom::Role::kTable); |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kAriaColumnCount, |
| "colcount", ax::mojom::Role::kGrid); |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kAriaColumnCount, |
| "colcount", ax::mojom::Role::kTreeGrid); |
| |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kAriaRowCount, "rowcount", |
| ax::mojom::Role::kTable); |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kAriaRowCount, "rowcount", |
| ax::mojom::Role::kGrid); |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kAriaRowCount, "rowcount", |
| ax::mojom::Role::kTreeGrid); |
| |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kAriaCellColumnIndex, |
| "colindex", ax::mojom::Role::kCell); |
| TestAtkObjectIntAttribute(root_node, root_atk_object, |
| ax::mojom::IntAttribute::kAriaCellRowIndex, |
| "rowindex", ax::mojom::Role::kCell); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| // |
| // AtkComponent tests |
| // |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkComponentRefAtPoint) { |
| AXNodeData root; |
| root.id = 0; |
| root.child_ids.push_back(1); |
| root.child_ids.push_back(2); |
| root.relative_bounds.bounds = gfx::RectF(0, 0, 30, 30); |
| |
| AXNodeData node1; |
| node1.id = 1; |
| node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10); |
| node1.SetName("Name1"); |
| |
| AXNodeData node2; |
| node2.id = 2; |
| node2.relative_bounds.bounds = gfx::RectF(20, 20, 10, 10); |
| node2.SetName("Name2"); |
| |
| Init(root, node1, node2); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_obj)); |
| EXPECT_TRUE(ATK_IS_COMPONENT(root_obj)); |
| g_object_ref(root_obj); |
| |
| AtkObject* child_obj = atk_component_ref_accessible_at_point( |
| ATK_COMPONENT(root_obj), 50, 50, ATK_XY_SCREEN); |
| EXPECT_EQ(nullptr, child_obj); |
| |
| // this is directly on node 1. |
| child_obj = atk_component_ref_accessible_at_point(ATK_COMPONENT(root_obj), 5, |
| 5, ATK_XY_SCREEN); |
| ASSERT_NE(nullptr, child_obj); |
| EXPECT_TRUE(ATK_IS_OBJECT(child_obj)); |
| |
| const gchar* name = atk_object_get_name(child_obj); |
| EXPECT_STREQ("Name1", name); |
| |
| g_object_unref(child_obj); |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkComponentsGetExtentsPositionSize) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kWindow; |
| root.relative_bounds.bounds = gfx::RectF(10, 40, 800, 600); |
| root.child_ids.push_back(2); |
| |
| AXNodeData child; |
| child.id = 2; |
| child.relative_bounds.bounds = gfx::RectF(100, 150, 200, 200); |
| Init(root, child); |
| |
| TestAXNodeWrapper::SetGlobalCoordinateOffset(gfx::Vector2d(100, 200)); |
| |
| AtkObject* root_obj = GetRootAtkObject(); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| ASSERT_TRUE(ATK_IS_COMPONENT(root_obj)); |
| g_object_ref(root_obj); |
| |
| gint x_left, y_top, width, height; |
| atk_component_get_extents(ATK_COMPONENT(root_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(110, x_left); |
| EXPECT_EQ(240, y_top); |
| EXPECT_EQ(800, width); |
| EXPECT_EQ(600, height); |
| |
| atk_component_get_position(ATK_COMPONENT(root_obj), &x_left, &y_top, |
| ATK_XY_SCREEN); |
| EXPECT_EQ(110, x_left); |
| EXPECT_EQ(240, y_top); |
| |
| atk_component_get_extents(ATK_COMPONENT(root_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_WINDOW); |
| EXPECT_EQ(110, x_left); |
| EXPECT_EQ(240, y_top); |
| EXPECT_EQ(800, width); |
| EXPECT_EQ(600, height); |
| |
| atk_component_get_position(ATK_COMPONENT(root_obj), &x_left, &y_top, |
| ATK_XY_WINDOW); |
| EXPECT_EQ(110, x_left); |
| EXPECT_EQ(240, y_top); |
| |
| atk_component_get_size(ATK_COMPONENT(root_obj), &width, &height); |
| EXPECT_EQ(800, width); |
| EXPECT_EQ(600, height); |
| |
| AXNode* child_node = GetRootNode()->children()[0]; |
| AtkObject* child_obj = AtkObjectFromNode(child_node); |
| ASSERT_TRUE(ATK_IS_OBJECT(child_obj)); |
| ASSERT_TRUE(ATK_IS_COMPONENT(child_obj)); |
| g_object_ref(child_obj); |
| |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(200, x_left); |
| EXPECT_EQ(350, y_top); |
| EXPECT_EQ(200, width); |
| EXPECT_EQ(200, height); |
| |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_WINDOW); |
| EXPECT_EQ(90, x_left); |
| EXPECT_EQ(110, y_top); |
| EXPECT_EQ(200, width); |
| EXPECT_EQ(200, height); |
| |
| atk_component_get_extents(ATK_COMPONENT(child_obj), nullptr, &y_top, &width, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(200, height); |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, nullptr, &width, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(200, x_left); |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, nullptr, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(350, y_top); |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, &width, |
| nullptr, ATK_XY_SCREEN); |
| EXPECT_EQ(200, width); |
| |
| g_object_unref(child_obj); |
| g_object_unref(root_obj); |
| |
| // Un-set the global offset so that it doesn't affect subsequent tests. |
| TestAXNodeWrapper::SetGlobalCoordinateOffset(gfx::Vector2d(0, 0)); |
| } |
| |
| #if ATK_CHECK_VERSION(2, 30, 0) |
| typedef bool (*ScrollToPointFunc)(AtkComponent* component, |
| AtkCoordType coords, |
| gint x, |
| gint y); |
| typedef bool (*ScrollToFunc)(AtkComponent* component, AtkScrollType type); |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, AtkComponentScrollToPoint) { |
| // There's a chance we may be compiled with a newer version of ATK and then |
| // run with an older one, so we need to do a runtime check for this method |
| // that is available in ATK 2.30 instead of linking directly. |
| ScrollToPointFunc scroll_to_point = reinterpret_cast<ScrollToPointFunc>( |
| dlsym(RTLD_DEFAULT, "atk_component_scroll_to_point")); |
| if (!scroll_to_point) { |
| LOG(WARNING) << "Skipping AtkComponentScrollToPoint" |
| " because ATK version < 2.30 detected."; |
| return; |
| } |
| |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.relative_bounds.bounds = gfx::RectF(0, 0, 2000, 2000); |
| |
| AXNodeData child1; |
| child1.id = 2; |
| child1.role = ax::mojom::Role::kStaticText; |
| child1.relative_bounds.bounds = gfx::RectF(10, 10, 10, 10); |
| root.child_ids.push_back(2); |
| |
| Init(root, child1); |
| |
| AXNode* child_node = GetRootNode()->children()[0]; |
| AtkObject* child_obj = AtkObjectFromNode(child_node); |
| ASSERT_TRUE(ATK_IS_OBJECT(child_obj)); |
| ASSERT_TRUE(ATK_IS_COMPONENT(child_obj)); |
| g_object_ref(child_obj); |
| |
| int x_left, y_top, width, height; |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(10, x_left); |
| EXPECT_EQ(10, y_top); |
| EXPECT_EQ(10, width); |
| EXPECT_EQ(10, height); |
| |
| scroll_to_point(ATK_COMPONENT(child_obj), ATK_XY_SCREEN, 600, 650); |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(610, x_left); |
| EXPECT_EQ(660, y_top); |
| EXPECT_EQ(10, width); |
| EXPECT_EQ(10, height); |
| |
| scroll_to_point(ATK_COMPONENT(child_obj), ATK_XY_PARENT, 10, 10); |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_SCREEN); |
| // The test wrapper scrolls every element when scrolling, so this should be |
| // 10 pixels to bottom and left of the current coordinates of the root. |
| EXPECT_EQ(620, x_left); |
| EXPECT_EQ(670, y_top); |
| EXPECT_EQ(10, width); |
| EXPECT_EQ(10, height); |
| |
| g_object_unref(child_obj); |
| |
| // Un-set the global offset so that it doesn't affect subsequent tests. |
| TestAXNodeWrapper::SetGlobalCoordinateOffset(gfx::Vector2d(0, 0)); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, AtkComponentScrollTo) { |
| // There's a chance we may be compiled with a newer version of ATK and then |
| // run with an older one, so we need to do a runtime check for this method |
| // that is available in ATK 2.30 instead of linking directly. |
| ScrollToFunc scroll_to = reinterpret_cast<ScrollToFunc>( |
| dlsym(RTLD_DEFAULT, "atk_component_scroll_to")); |
| if (!scroll_to) { |
| LOG(WARNING) << "Skipping AtkComponentScrollTo" |
| " because ATK version < 2.30 detected."; |
| return; |
| } |
| |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.relative_bounds.bounds = gfx::RectF(0, 0, 2000, 2000); |
| |
| AXNodeData child1; |
| child1.id = 2; |
| child1.role = ax::mojom::Role::kStaticText; |
| child1.relative_bounds.bounds = gfx::RectF(10, 10, 10, 10); |
| root.child_ids.push_back(2); |
| |
| Init(root, child1); |
| |
| AXNode* child_node = GetRootNode()->children()[0]; |
| AtkObject* child_obj = AtkObjectFromNode(child_node); |
| ASSERT_TRUE(ATK_IS_OBJECT(child_obj)); |
| ASSERT_TRUE(ATK_IS_COMPONENT(child_obj)); |
| g_object_ref(child_obj); |
| |
| int x_left, y_top, width, height; |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(10, x_left); |
| EXPECT_EQ(10, y_top); |
| EXPECT_EQ(10, width); |
| EXPECT_EQ(10, height); |
| |
| scroll_to(ATK_COMPONENT(child_obj), ATK_SCROLL_ANYWHERE); |
| atk_component_get_extents(ATK_COMPONENT(child_obj), &x_left, &y_top, &width, |
| &height, ATK_XY_SCREEN); |
| EXPECT_EQ(0, x_left); |
| EXPECT_EQ(0, y_top); |
| EXPECT_EQ(10, width); |
| EXPECT_EQ(10, height); |
| |
| // Un-set the global offset so that it doesn't affect subsequent tests. |
| TestAXNodeWrapper::SetGlobalCoordinateOffset(gfx::Vector2d(0, 0)); |
| } |
| #endif // ATK_CHECK_VERSION(2, 30, 0) |
| |
| // |
| // AtkValue tests |
| // |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkValueGetCurrentValue) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kSlider; |
| root.AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, 5.0); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| ASSERT_TRUE(ATK_IS_VALUE(root_obj)); |
| g_object_ref(root_obj); |
| |
| GValue current_value = G_VALUE_INIT; |
| atk_value_get_current_value(ATK_VALUE(root_obj), ¤t_value); |
| |
| EXPECT_EQ(G_TYPE_FLOAT, G_VALUE_TYPE(¤t_value)); |
| EXPECT_EQ(5.0, g_value_get_float(¤t_value)); |
| |
| g_value_unset(¤t_value); |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkValueGetMaximumValue) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kSlider; |
| root.AddFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange, 5.0); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| ASSERT_TRUE(ATK_IS_VALUE(root_obj)); |
| g_object_ref(root_obj); |
| |
| GValue max_value = G_VALUE_INIT; |
| atk_value_get_maximum_value(ATK_VALUE(root_obj), &max_value); |
| |
| EXPECT_EQ(G_TYPE_FLOAT, G_VALUE_TYPE(&max_value)); |
| EXPECT_EQ(5.0, g_value_get_float(&max_value)); |
| |
| g_value_unset(&max_value); |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkValueGetMinimumValue) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kSlider; |
| root.AddFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange, 5.0); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| ASSERT_TRUE(ATK_IS_VALUE(root_obj)); |
| g_object_ref(root_obj); |
| |
| GValue min_value = G_VALUE_INIT; |
| atk_value_get_minimum_value(ATK_VALUE(root_obj), &min_value); |
| |
| EXPECT_EQ(G_TYPE_FLOAT, G_VALUE_TYPE(&min_value)); |
| EXPECT_EQ(5.0, g_value_get_float(&min_value)); |
| |
| g_value_unset(&min_value); |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkValueGetMinimumIncrement) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kSlider; |
| root.AddFloatAttribute(ax::mojom::FloatAttribute::kStepValueForRange, 5.0); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| ASSERT_TRUE(ATK_IS_VALUE(root_obj)); |
| g_object_ref(root_obj); |
| |
| GValue increment = G_VALUE_INIT; |
| atk_value_get_minimum_increment(ATK_VALUE(root_obj), &increment); |
| |
| EXPECT_EQ(G_TYPE_FLOAT, G_VALUE_TYPE(&increment)); |
| EXPECT_EQ(5.0, g_value_get_float(&increment)); |
| |
| g_value_unset(&increment); |
| g_object_unref(root_obj); |
| } |
| |
| // |
| // AtkHyperlinkImpl interface |
| // |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkHyperlink) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kLink; |
| root.AddStringAttribute(ax::mojom::StringAttribute::kUrl, "http://foo.com"); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| ASSERT_TRUE(ATK_IS_HYPERLINK_IMPL(root_obj)); |
| g_object_ref(root_obj); |
| |
| AtkHyperlink* hyperlink( |
| atk_hyperlink_impl_get_hyperlink(ATK_HYPERLINK_IMPL(root_obj))); |
| ASSERT_TRUE(ATK_IS_HYPERLINK(hyperlink)); |
| |
| EXPECT_EQ(1, atk_hyperlink_get_n_anchors(hyperlink)); |
| gchar* uri = atk_hyperlink_get_uri(hyperlink, 0); |
| EXPECT_STREQ("http://foo.com", uri); |
| g_free(uri); |
| |
| g_object_unref(hyperlink); |
| g_object_unref(root_obj); |
| } |
| |
| // |
| // AtkText interface |
| // |
| // |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextCharacterGranularity) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kTextField; |
| root.AddStringAttribute(ax::mojom::StringAttribute::kValue, |
| "A decently long string \xE2\x98\xBA with an emoji."); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| ASSERT_TRUE(ATK_IS_TEXT(root_obj)); |
| AtkText* atk_text = ATK_TEXT(root_obj); |
| |
| EXPECT_EQ(static_cast<gunichar>('d'), |
| atk_text_get_character_at_offset(atk_text, 2)); |
| EXPECT_EQ(0u, atk_text_get_character_at_offset(atk_text, -1)); |
| EXPECT_EQ(0u, atk_text_get_character_at_offset(atk_text, 42342)); |
| EXPECT_EQ(0x263Au, atk_text_get_character_at_offset(atk_text, 23)); |
| EXPECT_EQ(static_cast<gunichar>(' '), |
| atk_text_get_character_at_offset(atk_text, 24)); |
| |
| auto verify_text = [&](const char* expected_text, char* text, |
| int expected_start, int expected_end, int start, |
| int end) { |
| EXPECT_STREQ(expected_text, text); |
| EXPECT_EQ(start, expected_start); |
| EXPECT_EQ(end, expected_end); |
| g_free(text); |
| }; |
| |
| auto verify_text_at_offset = [&](const char* expected_text, int offset, |
| int expected_start, int expected_end) { |
| testing::Message message; |
| message << "While checking at offset " << offset; |
| SCOPED_TRACE(message); |
| |
| int start = 0, end = 0; |
| char* text = atk_text_get_text_at_offset( |
| atk_text, offset, ATK_TEXT_BOUNDARY_CHAR, &start, &end); |
| verify_text(expected_text, text, expected_start, expected_end, start, end); |
| }; |
| |
| verify_text_at_offset("d", 2, 2, 3); |
| verify_text_at_offset(nullptr, -1, -1, -1); |
| verify_text_at_offset(nullptr, 42342, -1, -1); |
| verify_text_at_offset("\xE2\x98\xBA", 23, 23, 24); |
| verify_text_at_offset(" ", 24, 24, 25); |
| |
| auto verify_text_after_offset = [&](const char* expected_text, int offset, |
| int expected_start, int expected_end) { |
| testing::Message message; |
| message << "While checking after offset " << offset; |
| SCOPED_TRACE(message); |
| |
| int start = 0, end = 0; |
| char* text = atk_text_get_text_after_offset( |
| atk_text, offset, ATK_TEXT_BOUNDARY_CHAR, &start, &end); |
| verify_text(expected_text, text, expected_start, expected_end, start, end); |
| }; |
| |
| verify_text_after_offset("d", 1, 2, 3); |
| verify_text_after_offset(nullptr, 42342, -1, -1); |
| verify_text_after_offset("\xE2\x98\xBA", 22, 23, 24); |
| verify_text_after_offset(" ", 23, 24, 25); |
| |
| // This boundary condition is enforced by ATK for some reason. |
| verify_text_after_offset(nullptr, -1, 0, 0); |
| |
| auto verify_text_before_offset = [&](const char* expected_text, int offset, |
| int expected_start, int expected_end) { |
| testing::Message message; |
| message << "While checking before offset " << offset; |
| SCOPED_TRACE(message); |
| |
| int start = 0, end = 0; |
| char* text = atk_text_get_text_before_offset( |
| atk_text, offset, ATK_TEXT_BOUNDARY_CHAR, &start, &end); |
| verify_text(expected_text, text, expected_start, expected_end, start, end); |
| }; |
| |
| verify_text_before_offset("d", 3, 2, 3); |
| verify_text_before_offset(nullptr, 42342, -1, -1); |
| verify_text_before_offset("\xE2\x98\xBA", 24, 23, 24); |
| verify_text_before_offset(" ", 25, 24, 25); |
| verify_text_after_offset(nullptr, -1, 0, 0); |
| |
| g_object_unref(root_obj); |
| } |
| |
| struct GetTextSegmentTest { |
| int offset; |
| const char* content; |
| int start_offset; |
| int end_offset; |
| }; |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextWordGranularity) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kTextField; |
| root.AddStringAttribute(ax::mojom::StringAttribute::kValue, |
| "A decently long string."); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| ASSERT_TRUE(ATK_IS_TEXT(root_obj)); |
| AtkText* atk_text = ATK_TEXT(root_obj); |
| |
| static GetTextSegmentTest tests[] = {{0, "A ", 0, 2}, |
| {2, "decently ", 2, 11}, |
| {-1, nullptr, -1, -1}, |
| {1000, nullptr, -1, -1}}; |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| testing::Message message; |
| message << "While checking at index " << tests[i].offset << " for \'" |
| << tests[i].content << "\' at " << tests[i].start_offset << '-' |
| << tests[i].end_offset << '.'; |
| SCOPED_TRACE(message); |
| |
| int start_offset = -1, end_offset = -1; |
| char* content = atk_text_get_text_at_offset(atk_text, tests[i].offset, |
| ATK_TEXT_BOUNDARY_WORD_START, |
| &start_offset, &end_offset); |
| EXPECT_STREQ(content, tests[i].content); |
| EXPECT_EQ(start_offset, tests[i].start_offset); |
| EXPECT_EQ(end_offset, tests[i].end_offset); |
| g_free(content); |
| } |
| |
| #if ATK_CHECK_VERSION(2, 10, 0) |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| testing::Message message; |
| message << "While checking at index " << tests[i].offset << " for \'" |
| << tests[i].content << "\' at " << tests[i].start_offset << '-' |
| << tests[i].end_offset << '.'; |
| SCOPED_TRACE(message); |
| |
| int start_offset = -1, end_offset = -1; |
| char* content = atk_text_get_string_at_offset(atk_text, tests[i].offset, |
| ATK_TEXT_GRANULARITY_WORD, |
| &start_offset, &end_offset); |
| ASSERT_STREQ(content, tests[i].content) << "with test index=" << i; |
| ASSERT_EQ(start_offset, tests[i].start_offset) << "with test index=" << i; |
| ASSERT_EQ(end_offset, tests[i].end_offset) << "with test index=" << i; |
| g_free(content); |
| } |
| #endif |
| |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextSentenceGranularity) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kTextField; |
| root.AddStringAttribute(ax::mojom::StringAttribute::kValue, |
| "A short sentence. Another sentence. A third..."); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| ASSERT_TRUE(ATK_IS_TEXT(root_obj)); |
| AtkText* atk_text = ATK_TEXT(root_obj); |
| |
| static GetTextSegmentTest tests[] = { |
| {0, "A short sentence. ", 0, 18}, |
| {20, "Another sentence. ", 18, 40}, |
| {37, "Another sentence. ", 18, 40}, |
| {49, "A third...", 40, 50}, |
| {-1, nullptr, -1, -1}, |
| {-1000, nullptr, -1, -1}, |
| {1000, nullptr, -1, -1}, |
| }; |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| testing::Message message; |
| message << "While checking at index " << tests[i].offset << " for \'" |
| << tests[i].content << "\' at " << tests[i].start_offset << '-' |
| << tests[i].end_offset << '.'; |
| SCOPED_TRACE(message); |
| |
| int start_offset = -1, end_offset = -1; |
| char* content = atk_text_get_text_at_offset( |
| atk_text, tests[i].offset, ATK_TEXT_BOUNDARY_SENTENCE_START, |
| &start_offset, &end_offset); |
| ASSERT_STREQ(content, tests[i].content); |
| ASSERT_EQ(start_offset, tests[i].start_offset); |
| ASSERT_EQ(end_offset, tests[i].end_offset); |
| g_free(content); |
| } |
| |
| #if ATK_CHECK_VERSION(2, 10, 0) |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| testing::Message message; |
| message << "While checking at index " << tests[i].offset << " for \'" |
| << tests[i].content << "\' at " << tests[i].start_offset << '-' |
| << tests[i].end_offset << '.'; |
| SCOPED_TRACE(message); |
| |
| int start_offset = -1, end_offset = -1; |
| char* content = atk_text_get_string_at_offset(atk_text, tests[i].offset, |
| ATK_TEXT_GRANULARITY_SENTENCE, |
| &start_offset, &end_offset); |
| ASSERT_STREQ(content, tests[i].content); |
| ASSERT_EQ(start_offset, tests[i].start_offset); |
| ASSERT_EQ(end_offset, tests[i].end_offset); |
| g_free(content); |
| } |
| #endif |
| |
| g_object_unref(root_obj); |
| } |
| |
| #if ATK_CHECK_VERSION(2, 10, 0) |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextParagraphGranularity) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kTextField; |
| root.AddStringAttribute( |
| ax::mojom::StringAttribute::kValue, |
| "A short paragraph. \nAnother paragraph.\nA third..."); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| ASSERT_TRUE(ATK_IS_TEXT(root_obj)); |
| AtkText* atk_text = ATK_TEXT(root_obj); |
| |
| static GetTextSegmentTest tests[] = { |
| {0, "A short paragraph. ", 0, 19}, |
| {25, "Another paragraph.", 20, 38}, |
| {-1, nullptr, -1, -1}, |
| {12345, nullptr, -1, -1}, |
| }; |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| int start_offset = -1, end_offset = -1; |
| char* content = atk_text_get_string_at_offset( |
| atk_text, tests[i].offset, ATK_TEXT_GRANULARITY_PARAGRAPH, |
| &start_offset, &end_offset); |
| ASSERT_STREQ(content, tests[i].content) << "with test index=" << i; |
| ASSERT_EQ(start_offset, tests[i].start_offset) << "with test index=" << i; |
| ASSERT_EQ(end_offset, tests[i].end_offset) << "with test index=" << i; |
| g_free(content); |
| } |
| #endif |
| |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextWithNonBMPCharacters) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kTextField; |
| |
| // The playing card emoji in this string should be considered a single |
| // character offset for all AtkText API calls. |
| static const char root_text[] = |
| "\xF0\x9F\x83\x8f a decently long \xF0\x9F\x83\x8f string " |
| "\xF0\x9F\x83\x8f."; |
| root.AddStringAttribute(ax::mojom::StringAttribute::kValue, root_text); |
| Init(root); |
| |
| AtkObject* root_obj(GetRootAtkObject()); |
| ASSERT_TRUE(ATK_IS_OBJECT(root_obj)); |
| g_object_ref(root_obj); |
| |
| ASSERT_TRUE(ATK_IS_TEXT(root_obj)); |
| AtkText* atk_text = ATK_TEXT(root_obj); |
| |
| int root_text_length = g_utf8_strlen(root_text, -1); |
| ASSERT_EQ(atk_text_get_character_count(atk_text), root_text_length); |
| |
| for (int i = 0; i < root_text_length; i++) { |
| testing::Message message; |
| message << "Checking character at offset " << i; |
| SCOPED_TRACE(message); |
| |
| gunichar character = atk_text_get_character_at_offset(atk_text, i); |
| gunichar expected_character = |
| g_utf8_get_char_validated(g_utf8_offset_to_pointer(root_text, i), -1); |
| ASSERT_EQ(character, expected_character); |
| |
| int start_offset = -1, end_offset = -1; |
| char* char_string = atk_text_get_text_at_offset( |
| atk_text, i, ATK_TEXT_BOUNDARY_CHAR, &start_offset, &end_offset); |
| character = g_utf8_get_char_validated(char_string, -1); |
| ASSERT_EQ(character, expected_character); |
| ASSERT_EQ(start_offset, i); |
| ASSERT_EQ(end_offset, i + 1); |
| g_free(char_string); |
| |
| #if ATK_CHECK_VERSION(2, 10, 0) |
| start_offset = -1; |
| end_offset = -1; |
| char_string = atk_text_get_string_at_offset( |
| atk_text, i, ATK_TEXT_GRANULARITY_CHAR, &start_offset, &end_offset); |
| |
| character = g_utf8_get_char_validated(char_string, -1); |
| ASSERT_EQ(character, expected_character); |
| ASSERT_EQ(start_offset, i); |
| ASSERT_EQ(end_offset, i + 1); |
| g_free(char_string); |
| #endif |
| } |
| |
| static GetTextSegmentTest tests[] = {{0, "\xF0\x9F\x83\x8f ", 0, 2}, |
| {6, "decently ", 4, 13}}; |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) { |
| int start_offset = -1, end_offset = -1; |
| char* word = atk_text_get_text_at_offset(atk_text, tests[i].offset, |
| ATK_TEXT_BOUNDARY_WORD_START, |
| &start_offset, &end_offset); |
| testing::Message message; |
| message << "Checking test with index=" << i << " and expected text=\'" |
| << tests[i].content << "\' at " << tests[1].start_offset << '-' |
| << tests[1].end_offset << '.'; |
| SCOPED_TRACE(message); |
| |
| ASSERT_STREQ(word, tests[i].content); |
| ASSERT_EQ(start_offset, tests[i].start_offset); |
| ASSERT_EQ(end_offset, tests[i].end_offset); |
| |
| g_free(word); |
| } |
| |
| g_object_unref(root_obj); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextCaretMoved) { |
| Init(BuildTextField()); |
| |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| ASSERT_TRUE(ATK_IS_TEXT(root_atk_object)); |
| AtkText* atk_text = ATK_TEXT(root_atk_object); |
| |
| int caret_position_from_event = -1; |
| g_signal_connect(atk_text, "text-caret-moved", |
| G_CALLBACK(+[](AtkText*, int new_position, gpointer data) { |
| int* caret_position_from_event = static_cast<int*>(data); |
| *caret_position_from_event = new_position; |
| }), |
| &caret_position_from_event); |
| |
| atk_text_set_caret_offset(atk_text, 4); |
| ASSERT_EQ(atk_text_get_caret_offset(atk_text), 4); |
| ASSERT_EQ(caret_position_from_event, 4); |
| |
| // Setting the same position should not trigger another event. |
| caret_position_from_event = -1; |
| atk_text_set_caret_offset(atk_text, 4); |
| ASSERT_EQ(atk_text_get_caret_offset(atk_text), 4); |
| ASSERT_EQ(caret_position_from_event, -1); |
| |
| int character_count = atk_text_get_character_count(atk_text); |
| atk_text_set_caret_offset(atk_text, -1); |
| ASSERT_EQ(atk_text_get_caret_offset(atk_text), character_count); |
| ASSERT_EQ(caret_position_from_event, character_count); |
| atk_text_set_caret_offset(atk_text, 0); // Reset position. |
| |
| caret_position_from_event = -1; |
| atk_text_set_caret_offset(atk_text, -1000); |
| ASSERT_EQ(atk_text_get_caret_offset(atk_text), character_count); |
| ASSERT_EQ(caret_position_from_event, character_count); |
| atk_text_set_caret_offset(atk_text, 0); // Reset position. |
| |
| caret_position_from_event = -1; |
| atk_text_set_caret_offset(atk_text, 1000); |
| ASSERT_EQ(atk_text_get_caret_offset(atk_text), character_count); |
| ASSERT_EQ(caret_position_from_event, character_count); |
| |
| caret_position_from_event = -1; |
| atk_text_set_caret_offset(atk_text, character_count - 1); |
| ASSERT_EQ(atk_text_get_caret_offset(atk_text), character_count - 1); |
| ASSERT_EQ(caret_position_from_event, character_count - 1); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| class ActivationTester { |
| public: |
| explicit ActivationTester(AtkObject* target) : target_(target) { |
| auto callback = G_CALLBACK(+[](AtkWindow*, bool* flag) { *flag = true; }); |
| activate_id_ = |
| g_signal_connect(target, "activate", callback, &saw_activate_); |
| deactivate_id_ = |
| g_signal_connect(target, "deactivate", callback, &saw_deactivate_); |
| |
| DCHECK(activate_id_); |
| DCHECK(deactivate_id_); |
| DCHECK(activate_id_ != deactivate_id_); |
| } |
| |
| bool IsActivatedInStateSet() { |
| AtkStateSet* state_set = atk_object_ref_state_set(target_); |
| EXPECT_TRUE(ATK_IS_STATE_SET(state_set)); |
| bool in_state_set = |
| atk_state_set_contains_state(state_set, ATK_STATE_ACTIVE); |
| g_object_unref(state_set); |
| return in_state_set; |
| } |
| |
| void Reset() { |
| saw_activate_ = false; |
| saw_deactivate_ = false; |
| } |
| |
| virtual ~ActivationTester() { |
| g_signal_handler_disconnect(target_, activate_id_); |
| g_signal_handler_disconnect(target_, deactivate_id_); |
| } |
| |
| AtkObject* target_; |
| bool saw_activate_ = false; |
| bool saw_deactivate_ = false; |
| gulong activate_id_ = 0; |
| gulong deactivate_id_ = 0; |
| }; |
| |
| // |
| // AtkWindow interface and active state |
| // |
| // |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkWindowActive) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kWindow; |
| root.child_ids.push_back(2); |
| |
| AXNodeData child; |
| child.id = 2; |
| child.role = ax::mojom::Role::kCheckBox; |
| |
| Init(root, child); |
| |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| EXPECT_TRUE(ATK_IS_WINDOW(root_atk_object)); |
| |
| AXNode* checkbox_node = GetRootNode()->children()[0]; |
| AtkObject* checkbox_atk_obj = AtkObjectFromNode(checkbox_node); |
| |
| // Focus the checkbox to ensure that it also gets new focus events when |
| // the toplevel window goes from unfocused to focused. |
| GetPlatformNode(checkbox_node) |
| ->NotifyAccessibilityEvent(ax::mojom::Event::kFocus); |
| |
| bool saw_active_focus_state_change = false; |
| g_signal_connect(checkbox_atk_obj, "state-change", |
| G_CALLBACK(+[](AtkObject* atkobject, gchar* state_changed, |
| gboolean new_value, bool* flag) { |
| if (!g_strcmp0(state_changed, "focused") && new_value) |
| *flag = true; |
| }), |
| &saw_active_focus_state_change); |
| |
| { |
| ActivationTester tester(root_atk_object); |
| EXPECT_FALSE(tester.IsActivatedInStateSet()); |
| static_cast<AXPlatformNodeAuraLinux*>(GetRootPlatformNode()) |
| ->NotifyAccessibilityEvent(ax::mojom::Event::kWindowActivated); |
| EXPECT_TRUE(tester.saw_activate_); |
| EXPECT_FALSE(tester.saw_deactivate_); |
| EXPECT_TRUE(tester.IsActivatedInStateSet()); |
| EXPECT_TRUE(saw_active_focus_state_change); |
| } |
| |
| { |
| saw_active_focus_state_change = false; |
| |
| ActivationTester tester(root_atk_object); |
| static_cast<AXPlatformNodeAuraLinux*>(GetRootPlatformNode()) |
| ->NotifyAccessibilityEvent(ax::mojom::Event::kWindowDeactivated); |
| EXPECT_FALSE(tester.saw_activate_); |
| EXPECT_TRUE(tester.saw_deactivate_); |
| EXPECT_FALSE(tester.IsActivatedInStateSet()); |
| EXPECT_FALSE(saw_active_focus_state_change); |
| } |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| // |
| // AtkWindow interface and iconified state |
| // |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkWindowMinimized) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kWindow; |
| Init(root); |
| |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| EXPECT_TRUE(ATK_IS_WINDOW(root_atk_object)); |
| |
| AtkStateSet* state_set = atk_object_ref_state_set(root_atk_object); |
| EXPECT_TRUE(ATK_IS_STATE_SET(state_set)); |
| EXPECT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_ICONIFIED)); |
| g_object_unref(state_set); |
| |
| GetRootWrapper()->set_minimized(true); |
| |
| state_set = atk_object_ref_state_set(root_atk_object); |
| EXPECT_TRUE(ATK_IS_STATE_SET(state_set)); |
| EXPECT_TRUE(atk_state_set_contains_state(state_set, ATK_STATE_ICONIFIED)); |
| g_object_unref(state_set); |
| |
| bool saw_state_change = false; |
| g_signal_connect(root_atk_object, "state-change", |
| G_CALLBACK(+[](AtkObject* atkobject, gchar* state_changed, |
| gboolean new_value, bool* flag) { |
| if (!g_strcmp0(state_changed, "iconified")) |
| *flag = true; |
| }), |
| &saw_state_change); |
| |
| AXPlatformNodeAuraLinux* root_node = GetRootPlatformNode(); |
| static_cast<AXPlatformNodeAuraLinux*>(root_node)->NotifyAccessibilityEvent( |
| ax::mojom::Event::kWindowVisibilityChanged); |
| |
| EXPECT_TRUE(saw_state_change); |
| |
| saw_state_change = false; |
| static_cast<AXPlatformNodeAuraLinux*>(root_node)->NotifyAccessibilityEvent( |
| ax::mojom::Event::kWindowVisibilityChanged); |
| EXPECT_FALSE(saw_state_change); |
| |
| GetRootWrapper()->set_minimized(false); |
| static_cast<AXPlatformNodeAuraLinux*>(root_node)->NotifyAccessibilityEvent( |
| ax::mojom::Event::kWindowVisibilityChanged); |
| EXPECT_TRUE(saw_state_change); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestFocusTriggersAtkWindowActive) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kWindow; |
| root.child_ids.push_back(2); |
| |
| AXNodeData child_node_data; |
| child_node_data.id = 2; |
| child_node_data.role = ax::mojom::Role::kButton; |
| |
| Init(root, child_node_data); |
| |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| EXPECT_TRUE(ATK_IS_WINDOW(root_atk_object)); |
| |
| g_object_ref(root_atk_object); |
| |
| AXNode* child_node = GetRootNode()->children()[0]; |
| |
| // A focus event on a child node should not cause the window to |
| // activate. |
| { |
| ActivationTester tester(root_atk_object); |
| GetPlatformNode(child_node) |
| ->NotifyAccessibilityEvent(ax::mojom::Event::kFocus); |
| EXPECT_FALSE(tester.saw_activate_); |
| EXPECT_FALSE(tester.saw_deactivate_); |
| EXPECT_FALSE(tester.IsActivatedInStateSet()); |
| } |
| |
| // A focus event on the window itself should cause the window to activate. |
| { |
| ActivationTester tester(root_atk_object); |
| GetRootPlatformNode()->NotifyAccessibilityEvent(ax::mojom::Event::kFocus); |
| EXPECT_TRUE(tester.saw_activate_); |
| EXPECT_FALSE(tester.saw_deactivate_); |
| EXPECT_TRUE(tester.IsActivatedInStateSet()); |
| } |
| |
| // Since the window is already active, we shouldn't see another activation |
| // event, but it should still be active. |
| { |
| ActivationTester tester(root_atk_object); |
| GetRootPlatformNode()->NotifyAccessibilityEvent(ax::mojom::Event::kFocus); |
| EXPECT_FALSE(tester.saw_activate_); |
| EXPECT_FALSE(tester.saw_deactivate_); |
| EXPECT_TRUE(tester.IsActivatedInStateSet()); |
| } |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkPopupWindowActive) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kApplication; |
| root.child_ids.push_back(2); |
| root.child_ids.push_back(3); |
| |
| AXNodeData window_node_data; |
| window_node_data.id = 2; |
| window_node_data.role = ax::mojom::Role::kWindow; |
| |
| AXNodeData menu_node_data; |
| menu_node_data.id = 3; |
| menu_node_data.role = ax::mojom::Role::kWindow; |
| menu_node_data.child_ids.push_back(4); |
| |
| AXNodeData menu_item_data; |
| menu_item_data.id = 4; |
| |
| Init(root, window_node_data, menu_node_data, menu_item_data); |
| |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| AXNode* window_node = GetRootNode()->children()[0]; |
| AtkObject* window_atk_node(AtkObjectFromNode(window_node)); |
| |
| ActivationTester toplevel_tester(window_atk_node); |
| GetPlatformNode(window_node) |
| ->NotifyAccessibilityEvent(ax::mojom::Event::kWindowActivated); |
| EXPECT_TRUE(toplevel_tester.saw_activate_); |
| EXPECT_FALSE(toplevel_tester.saw_deactivate_); |
| EXPECT_TRUE(toplevel_tester.IsActivatedInStateSet()); |
| |
| toplevel_tester.Reset(); |
| |
| AXNode* menu_node = GetRootNode()->children()[1]; |
| AtkObject* menu_atk_node(AtkObjectFromNode(menu_node)); |
| { |
| ActivationTester tester(menu_atk_node); |
| GetPlatformNode(menu_node)->NotifyAccessibilityEvent( |
| ax::mojom::Event::kMenuPopupStart); |
| EXPECT_TRUE(tester.saw_activate_); |
| EXPECT_FALSE(tester.saw_deactivate_); |
| EXPECT_TRUE(tester.IsActivatedInStateSet()); |
| } |
| |
| EXPECT_FALSE(toplevel_tester.saw_activate_); |
| EXPECT_TRUE(toplevel_tester.saw_deactivate_); |
| |
| toplevel_tester.Reset(); |
| |
| { |
| ActivationTester tester(menu_atk_node); |
| GetPlatformNode(menu_node)->NotifyAccessibilityEvent( |
| ax::mojom::Event::kMenuPopupHide); |
| EXPECT_FALSE(tester.saw_activate_); |
| EXPECT_TRUE(tester.saw_deactivate_); |
| EXPECT_FALSE(tester.IsActivatedInStateSet()); |
| } |
| |
| { |
| ActivationTester tester(menu_atk_node); |
| GetPlatformNode(menu_node)->NotifyAccessibilityEvent( |
| ax::mojom::Event::kMenuPopupEnd); |
| EXPECT_FALSE(tester.saw_activate_); |
| EXPECT_FALSE(tester.saw_deactivate_); |
| EXPECT_FALSE(tester.IsActivatedInStateSet()); |
| } |
| |
| // Now that the menu is definitively closed, activation should have returned |
| // to the previously activated toplevel frame. |
| EXPECT_TRUE(toplevel_tester.saw_activate_); |
| EXPECT_FALSE(toplevel_tester.saw_deactivate_); |
| |
| // No we test opening the menu and closing it without hiding any submenus. The |
| // toplevel should lose and then regain focus. |
| toplevel_tester.Reset(); |
| |
| GetPlatformNode(menu_node)->NotifyAccessibilityEvent( |
| ax::mojom::Event::kMenuPopupStart); |
| GetPlatformNode(menu_node)->NotifyAccessibilityEvent( |
| ax::mojom::Event::kMenuPopupEnd); |
| EXPECT_TRUE(toplevel_tester.saw_activate_); |
| EXPECT_TRUE(toplevel_tester.saw_deactivate_); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkSelectionInterface) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kListBox; |
| root.child_ids.push_back(2); |
| root.child_ids.push_back(3); |
| root.child_ids.push_back(4); |
| root.child_ids.push_back(5); |
| |
| AXNodeData item_1; |
| item_1.id = 2; |
| item_1.role = ax::mojom::Role::kListBoxOption; |
| |
| AXNodeData item_2; |
| item_2.id = 3; |
| item_2.role = ax::mojom::Role::kListBoxOption; |
| |
| AXNodeData item_3; |
| item_3.id = 4; |
| item_3.role = ax::mojom::Role::kListBoxOption; |
| |
| // Add a final item which is not selectable. |
| AXNodeData item_4; |
| item_4.id = 5; |
| item_4.role = ax::mojom::Role::kListItem; |
| |
| AXTreeUpdate update; |
| update.root_id = 1; |
| update.nodes.push_back(root); |
| update.nodes.push_back(item_1); |
| update.nodes.push_back(item_2); |
| update.nodes.push_back(item_3); |
| update.nodes.push_back(item_4); |
| Init(update); |
| |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| ASSERT_TRUE(ATK_IS_SELECTION(root_atk_object)); |
| |
| ASSERT_TRUE(ATK_IS_SELECTION(root_atk_object)); |
| AtkSelection* selection = ATK_SELECTION(root_atk_object); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 0); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 0)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 1)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 2)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 3)); |
| |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, -1)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, -100)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 4)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 3000)); |
| |
| ASSERT_TRUE(atk_selection_select_all_selection(selection)); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 3); |
| ASSERT_TRUE(atk_selection_is_child_selected(selection, 0)); |
| ASSERT_TRUE(atk_selection_is_child_selected(selection, 1)); |
| ASSERT_TRUE(atk_selection_is_child_selected(selection, 2)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 3)); |
| |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, -1)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, -100)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 4)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 3000)); |
| |
| ASSERT_TRUE(atk_selection_clear_selection(selection)); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 0); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 0)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 1)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 2)); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 3)); |
| |
| ASSERT_TRUE(atk_selection_add_selection(selection, 1)); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 1); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 0)); |
| ASSERT_TRUE(atk_selection_is_child_selected(selection, 1)); |
| |
| // The index to this function is the index into the selected elements, not |
| // into the children. |
| ASSERT_TRUE(atk_selection_remove_selection(selection, 0)); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 0); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 1)); |
| |
| // We should not be able to select an item with a role that is not |
| // selectable. |
| ASSERT_FALSE(atk_selection_add_selection(selection, 3)); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 0); |
| ASSERT_FALSE(atk_selection_is_child_selected(selection, 3)); |
| |
| // Test some out of bounds use of atk_selection_add_selection. |
| ASSERT_FALSE(atk_selection_add_selection(selection, -1)); |
| ASSERT_FALSE(atk_selection_add_selection(selection, -100)); |
| ASSERT_FALSE(atk_selection_add_selection(selection, 4)); |
| ASSERT_FALSE(atk_selection_add_selection(selection, 100)); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 0); |
| |
| ASSERT_TRUE(atk_selection_select_all_selection(selection)); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 3); |
| ASSERT_FALSE(atk_selection_remove_selection(selection, -1)); |
| ASSERT_FALSE(atk_selection_remove_selection(selection, -100)); |
| ASSERT_FALSE(atk_selection_remove_selection(selection, 4)); |
| ASSERT_FALSE(atk_selection_remove_selection(selection, 100)); |
| ASSERT_EQ(atk_selection_get_selection_count(selection), 3); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| // Tests GetPosInSet() and GetSetSize() functions of AXPlatformNodeBase. |
| // PosInSet and SetSize must be tested separately from other IntAttributes |
| // because they can be either assigned values or calculated dynamically. |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectSetSizePosInSet) { |
| AXTreeUpdate update; |
| update.root_id = 1; |
| update.nodes.resize(4); |
| update.nodes[0].id = 1; |
| update.nodes[0].role = ax::mojom::Role::kRadioGroup; |
| update.nodes[0].child_ids = {2, 3, 4}; |
| update.nodes[1].id = 2; |
| update.nodes[1].role = |
| ax::mojom::Role::kRadioButton; // kRadioButton posinset = 2, setsize = 5. |
| update.nodes[1].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 2); |
| update.nodes[2].id = 3; |
| update.nodes[2].role = |
| ax::mojom::Role::kRadioButton; // kRadioButton posinset = 3, setsize = 5. |
| update.nodes[3].id = 4; |
| update.nodes[3].role = |
| ax::mojom::Role::kRadioButton; // kRadioButton posinset = 5, stesize = 5 |
| update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 5); |
| Init(update); |
| |
| AXNode* radiobutton1 = GetRootNode()->children()[0]; |
| AtkObject* radiobutton1_atk_object(AtkObjectFromNode(radiobutton1)); |
| EXPECT_TRUE(ATK_IS_OBJECT(radiobutton1_atk_object)); |
| |
| AXNode* radiobutton2 = GetRootNode()->children()[1]; |
| AtkObject* radiobutton2_atk_object(AtkObjectFromNode(radiobutton2)); |
| EXPECT_TRUE(ATK_IS_OBJECT(radiobutton2_atk_object)); |
| |
| AXNode* radiobutton3 = GetRootNode()->children()[2]; |
| AtkObject* radiobutton3_atk_object(AtkObjectFromNode(radiobutton3)); |
| EXPECT_TRUE(ATK_IS_OBJECT(radiobutton3_atk_object)); |
| |
| // Notice that setsize was never assigned to any of the kRadioButtons, but was |
| // inferred. |
| EnsureAtkObjectHasAttributeWithValue(radiobutton1_atk_object, "posinset", |
| "2"); |
| EnsureAtkObjectHasAttributeWithValue(radiobutton1_atk_object, "setsize", "5"); |
| EnsureAtkObjectHasAttributeWithValue(radiobutton2_atk_object, "posinset", |
| "3"); |
| EnsureAtkObjectHasAttributeWithValue(radiobutton2_atk_object, "setsize", "5"); |
| EnsureAtkObjectHasAttributeWithValue(radiobutton3_atk_object, "posinset", |
| "5"); |
| EnsureAtkObjectHasAttributeWithValue(radiobutton3_atk_object, "setsize", "5"); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkRelations) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| root.AddIntAttribute(ax::mojom::IntAttribute::kDetailsId, 2); |
| |
| AXNodeData child1; |
| child1.id = 2; |
| child1.role = ax::mojom::Role::kStaticText; |
| |
| root.child_ids.push_back(2); |
| |
| AXNodeData child2; |
| child2.id = 3; |
| child2.role = ax::mojom::Role::kStaticText; |
| std::vector<int32_t> labelledby_ids = {1, 4}; |
| child2.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds, |
| labelledby_ids); |
| |
| root.child_ids.push_back(3); |
| |
| AXNodeData child3; |
| child3.id = 4; |
| child3.role = ax::mojom::Role::kStaticText; |
| child3.AddIntAttribute(ax::mojom::IntAttribute::kDetailsId, 2); |
| child3.AddIntAttribute(ax::mojom::IntAttribute::kMemberOfId, 1); |
| |
| root.child_ids.push_back(4); |
| |
| Init(root, child1, child2, child3); |
| |
| // We don't test relations that are too new for the runtime version of ATK. |
| GEnumClass* enum_class = |
| G_ENUM_CLASS(g_type_class_ref(atk_relation_type_get_type())); |
| int max_relation_type = enum_class->maximum; |
| g_type_class_unref(enum_class); |
| |
| auto assert_contains_relation = [&](AtkObject* object, AtkObject* target, |
| AtkRelationType relation) { |
| if (relation > max_relation_type) |
| return; |
| |
| AtkRelationSet* relations = atk_object_ref_relation_set(object); |
| ASSERT_TRUE(atk_relation_set_contains(relations, relation)); |
| ASSERT_TRUE(atk_relation_set_contains_target(relations, relation, target)); |
| g_object_unref(G_OBJECT(relations)); |
| }; |
| |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| EXPECT_TRUE(ATK_IS_OBJECT(root_atk_object)); |
| g_object_ref(root_atk_object); |
| |
| AtkObject* atk_child1(AtkObjectFromNode(GetRootNode()->children()[0])); |
| AtkObject* atk_child2(AtkObjectFromNode(GetRootNode()->children()[1])); |
| AtkObject* atk_child3(AtkObjectFromNode(GetRootNode()->children()[2])); |
| |
| assert_contains_relation(root_atk_object, atk_child1, ATK_RELATION_DETAILS); |
| assert_contains_relation(atk_child1, root_atk_object, |
| ATK_RELATION_DETAILS_FOR); |
| assert_contains_relation(atk_child3, atk_child1, ATK_RELATION_DETAILS); |
| assert_contains_relation(atk_child1, atk_child3, ATK_RELATION_DETAILS_FOR); |
| |
| assert_contains_relation(atk_child2, root_atk_object, |
| ATK_RELATION_LABELLED_BY); |
| assert_contains_relation(root_atk_object, atk_child2, ATK_RELATION_LABEL_FOR); |
| assert_contains_relation(atk_child2, atk_child3, ATK_RELATION_LABELLED_BY); |
| assert_contains_relation(atk_child3, atk_child2, ATK_RELATION_LABEL_FOR); |
| |
| assert_contains_relation(atk_child3, root_atk_object, ATK_RELATION_MEMBER_OF); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAllReverseAtkRelations) { |
| // We don't test relations that are too new for the runtime version of ATK. |
| GEnumClass* enum_class = |
| G_ENUM_CLASS(g_type_class_ref(atk_relation_type_get_type())); |
| int max_relation_type = enum_class->maximum; |
| g_type_class_unref(enum_class); |
| |
| auto test_relation = [&](auto attribute_setter, |
| AtkRelationType expected_relation, |
| AtkRelationType expected_reverse_relation) { |
| if (expected_relation > max_relation_type || |
| expected_reverse_relation > max_relation_type) |
| return; |
| |
| AXNodeData root_data; |
| root_data.id = 1; |
| root_data.role = ax::mojom::Role::kRootWebArea; |
| attribute_setter(&root_data, 2); |
| |
| AXNodeData child_data; |
| child_data.id = 2; |
| child_data.role = ax::mojom::Role::kStaticText; |
| root_data.child_ids.push_back(2); |
| Init(root_data, child_data); |
| |
| AtkObject* source(GetRootAtkObject()); |
| AtkObject* target(AtkObjectFromNode(GetRootNode()->children()[0])); |
| |
| AtkRelationSet* relations = atk_object_ref_relation_set(source); |
| ASSERT_TRUE(atk_relation_set_contains(relations, expected_relation)); |
| ASSERT_TRUE( |
| atk_relation_set_contains_target(relations, expected_relation, target)); |
| g_object_unref(G_OBJECT(relations)); |
| |
| relations = atk_object_ref_relation_set(target); |
| ASSERT_TRUE( |
| atk_relation_set_contains(relations, expected_reverse_relation)); |
| ASSERT_TRUE(atk_relation_set_contains_target( |
| relations, expected_reverse_relation, source)); |
| g_object_unref(G_OBJECT(relations)); |
| }; |
| |
| auto test_int_relation = [&](ax::mojom::IntAttribute relation, |
| AtkRelationType expected_relation, |
| AtkRelationType expected_reverse_relation) { |
| auto setter = [&](AXNodeData* data, int target_id) { |
| data->AddIntAttribute(relation, target_id); |
| }; |
| test_relation(setter, expected_relation, expected_reverse_relation); |
| }; |
| |
| auto test_int_list_relation = [&](ax::mojom::IntListAttribute relation, |
| AtkRelationType expected_relation, |
| AtkRelationType expected_reverse_relation) { |
| auto setter = [&](AXNodeData* data, int target_id) { |
| std::vector<int32_t> ids = {target_id}; |
| data->AddIntListAttribute(relation, ids); |
| }; |
| test_relation(setter, expected_relation, expected_reverse_relation); |
| }; |
| |
| test_int_relation(ax::mojom::IntAttribute::kDetailsId, ATK_RELATION_DETAILS, |
| ATK_RELATION_DETAILS_FOR); |
| test_int_relation(ax::mojom::IntAttribute::kErrormessageId, |
| ATK_RELATION_ERROR_MESSAGE, ATK_RELATION_ERROR_FOR); |
| test_int_list_relation(ax::mojom::IntListAttribute::kControlsIds, |
| ATK_RELATION_CONTROLLER_FOR, |
| ATK_RELATION_CONTROLLED_BY); |
| test_int_list_relation(ax::mojom::IntListAttribute::kDescribedbyIds, |
| ATK_RELATION_DESCRIBED_BY, |
| ATK_RELATION_DESCRIPTION_FOR); |
| test_int_list_relation(ax::mojom::IntListAttribute::kFlowtoIds, |
| ATK_RELATION_FLOWS_TO, ATK_RELATION_FLOWS_FROM); |
| test_int_list_relation(ax::mojom::IntListAttribute::kLabelledbyIds, |
| ATK_RELATION_LABELLED_BY, ATK_RELATION_LABEL_FOR); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextTextFieldGetNSelectionsZero) { |
| Init(BuildTextField()); |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| g_object_ref(root_atk_object); |
| |
| AtkText* atk_text = ATK_TEXT(root_atk_object); |
| ASSERT_NE(nullptr, atk_text); |
| EXPECT_EQ(0, atk_text_get_n_selections(atk_text)); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, |
| TestAtkTextContentEditableGetNSelectionsZero) { |
| Init(BuildContentEditable()); |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| g_object_ref(root_atk_object); |
| |
| AtkText* atk_text = ATK_TEXT(root_atk_object); |
| ASSERT_NE(nullptr, atk_text); |
| EXPECT_EQ(0, atk_text_get_n_selections(atk_text)); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextContentEditableGetNSelections) { |
| Init(BuildContentEditableWithSelectionRange(1, 2)); |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| g_object_ref(root_atk_object); |
| |
| AtkText* atk_text = ATK_TEXT(root_atk_object); |
| ASSERT_NE(nullptr, atk_text); |
| EXPECT_EQ(1, atk_text_get_n_selections(atk_text)); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextTextFieldSetSelection) { |
| Init(BuildTextField()); |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| g_object_ref(root_atk_object); |
| |
| AtkText* atk_text = ATK_TEXT(root_atk_object); |
| ASSERT_NE(nullptr, atk_text); |
| |
| bool saw_selection_change = false; |
| g_signal_connect( |
| atk_text, "text-selection-changed", |
| G_CALLBACK(+[](AtkObject* atkobject, bool* flag) { *flag = true; }), |
| &saw_selection_change); |
| |
| int selection_start, selection_end; |
| |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 1)); |
| EXPECT_TRUE(saw_selection_change); |
| g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end)); |
| EXPECT_EQ(selection_start, 0); |
| EXPECT_EQ(selection_end, 1); |
| |
| // Reset position. |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 0)); |
| |
| saw_selection_change = false; |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 1, 0)); |
| EXPECT_TRUE(saw_selection_change); |
| g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end)); |
| EXPECT_EQ(selection_start, 0); |
| EXPECT_EQ(selection_end, 1); |
| |
| // Setting the selection to the same location should not trigger |
| // another event. |
| saw_selection_change = false; |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 1, 0)); |
| EXPECT_FALSE(saw_selection_change); |
| g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end)); |
| EXPECT_EQ(selection_start, 0); |
| EXPECT_EQ(selection_end, 1); |
| |
| saw_selection_change = false; |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 2, 4)); |
| EXPECT_TRUE(saw_selection_change); |
| g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end)); |
| EXPECT_EQ(selection_start, 2); |
| EXPECT_EQ(selection_end, 4); |
| |
| saw_selection_change = false; |
| EXPECT_FALSE(atk_text_set_selection(atk_text, 1, 0, 0)); |
| EXPECT_FALSE(saw_selection_change); |
| g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end)); |
| EXPECT_EQ(selection_start, 2); |
| EXPECT_EQ(selection_end, 4); |
| |
| saw_selection_change = false; |
| EXPECT_FALSE(atk_text_set_selection(atk_text, 0, 0, 50)); |
| EXPECT_FALSE(saw_selection_change); |
| |
| saw_selection_change = false; |
| int n_characters = atk_text_get_character_count(atk_text); |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, -1)); |
| EXPECT_TRUE(saw_selection_change); |
| g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end)); |
| EXPECT_EQ(selection_start, 0); |
| EXPECT_EQ(selection_end, n_characters); |
| |
| saw_selection_change = false; |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 1)); |
| EXPECT_EQ(1, atk_text_get_n_selections(atk_text)); |
| EXPECT_TRUE(atk_text_remove_selection(atk_text, 0)); |
| EXPECT_TRUE(saw_selection_change); |
| EXPECT_EQ(0, atk_text_get_n_selections(atk_text)); |
| |
| // Reset position. |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 0)); |
| |
| saw_selection_change = false; |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 1)); |
| EXPECT_EQ(1, atk_text_get_n_selections(atk_text)); |
| EXPECT_FALSE(atk_text_remove_selection(atk_text, 1)); |
| EXPECT_TRUE(saw_selection_change); |
| EXPECT_EQ(1, atk_text_get_n_selections(atk_text)); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextTextFieldGetSelection) { |
| Init(BuildTextField()); |
| AtkObject* root_atk_object(GetRootAtkObject()); |
| g_object_ref(root_atk_object); |
| |
| AtkText* atk_text = ATK_TEXT(root_atk_object); |
| ASSERT_NE(nullptr, atk_text); |
| |
| int selection_start = 0, selection_end = 0; |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 3)); |
| gchar* selected_text = |
| atk_text_get_selection(atk_text, 0, &selection_start, &selection_end); |
| EXPECT_STREQ("How", selected_text); |
| EXPECT_EQ(selection_start, 0); |
| EXPECT_EQ(selection_end, 3); |
| g_free(selected_text); |
| |
| selection_start = 0; |
| selection_end = 0; |
| |
| EXPECT_TRUE(atk_text_remove_selection(atk_text, 0)); |
| selected_text = |
| atk_text_get_selection(atk_text, 0, &selection_start, &selection_end); |
| EXPECT_EQ(nullptr, selected_text); |
| EXPECT_EQ(selection_start, 0); |
| EXPECT_EQ(selection_end, 0); |
| |
| EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 3)); |
| |
| selected_text = |
| atk_text_get_selection(atk_text, 1, &selection_start, &selection_end); |
| EXPECT_EQ(nullptr, selected_text); |
| EXPECT_EQ(selection_start, 0); |
| EXPECT_EQ(selection_end, 0); |
| |
| selected_text = |
| atk_text_get_selection(atk_text, -1, &selection_start, &selection_end); |
| EXPECT_EQ(nullptr, selected_text); |
| EXPECT_EQ(selection_start, 0); |
| EXPECT_EQ(selection_end, 0); |
| |
| g_object_unref(root_atk_object); |
| } |
| |
| TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectExpandRebuildsPlatformNode) { |
| AXNodeData root_data; |
| root_data.id = 1; |
| root_data.role = ax::mojom::Role::kUnknown; |
| |
| Init(root_data); |
| |
| AtkObject* original_atk_object = GetRootAtkObject(); |
| ASSERT_TRUE(ATK_IS_OBJECT(original_atk_object)); |
| ASSERT_FALSE(ATK_IS_SELECTION(original_atk_object)); |
| g_object_ref(original_atk_object); |
| |
| root_data = AXNodeData(); |
| root_data.role = ax::mojom::Role::kListBox; |
| GetRootNode()->SetData(root_data); |
| |
| ASSERT_EQ(original_atk_object, GetRootAtkObject()); |
| |
| GetRootPlatformNode()->NotifyAccessibilityEvent( |
| ax::mojom::Event::kExpandedChanged); |
| |
| AtkObject* new_atk_object = GetRootAtkObject(); |
| ASSERT_NE(original_atk_object, new_atk_object); |
| ASSERT_TRUE(ATK_IS_SELECTION(new_atk_object)); |
| |
| g_object_unref(original_atk_object); |
| } |
| |
| } // namespace ui |