// Copyright 2021 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/accessibility/ax_computed_node_data.h" #include #include #include #include "base/memory/raw_ptr.h" #include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/ax_node_id_forward.h" #include "ui/accessibility/ax_position.h" #include "ui/accessibility/ax_tree.h" #include "ui/accessibility/ax_tree_data.h" #include "ui/accessibility/ax_tree_id.h" #include "ui/accessibility/test_ax_tree_manager.h" namespace ui { namespace { class AXComputedNodeDataTest : public ::testing::Test, public TestAXTreeManager { public: AXComputedNodeDataTest(); ~AXComputedNodeDataTest() override; AXComputedNodeDataTest(const AXComputedNodeDataTest& other) = delete; AXComputedNodeDataTest& operator=(const AXComputedNodeDataTest& other) = delete; void SetUp() override; protected: // Numbers at the end of variable names indicate their position under the // root. AXNodeData root_; AXNodeData paragraph_0_; AXNodeData static_text_0_0_ignored_; AXNodeData paragraph_1_ignored_; AXNodeData static_text_1_0_; AXNodeData paragraph_2_ignored_; AXNodeData link_2_0_ignored_; AXNodeData static_text_2_0_0_; AXNodeData static_text_2_0_1_; raw_ptr root_node_; }; AXComputedNodeDataTest::AXComputedNodeDataTest() = default; AXComputedNodeDataTest::~AXComputedNodeDataTest() = default; void AXComputedNodeDataTest::SetUp() { // ++kRootWebArea contenteditable // ++++kParagraph // ++++++kStaticText IGNORED "i" // ++++kParagraph IGNORED // ++++++kStaticText "t_1" // ++++kParagraph IGNORED // ++++++kLink IGNORED // ++++++++kStaticText "s+t++2...0. 0" // ++++++++kStaticText "s t\n2\r0\r\n1" root_.id = 1; paragraph_0_.id = 2; static_text_0_0_ignored_.id = 3; paragraph_1_ignored_.id = 4; static_text_1_0_.id = 5; paragraph_2_ignored_.id = 6; link_2_0_ignored_.id = 7; static_text_2_0_0_.id = 8; static_text_2_0_1_.id = 9; root_.role = ax::mojom::Role::kRootWebArea; root_.AddBoolAttribute(ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot, true); root_.child_ids = {paragraph_0_.id, paragraph_1_ignored_.id, paragraph_2_ignored_.id}; paragraph_0_.role = ax::mojom::Role::kParagraph; paragraph_0_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true); paragraph_0_.child_ids = {static_text_0_0_ignored_.id}; static_text_0_0_ignored_.role = ax::mojom::Role::kStaticText; static_text_0_0_ignored_.AddState(ax::mojom::State::kIgnored); // Ignored text should not appear anywhere and should not be used to separate // words. static_text_0_0_ignored_.SetName("i"); paragraph_1_ignored_.role = ax::mojom::Role::kParagraph; paragraph_1_ignored_.AddState(ax::mojom::State::kIgnored); paragraph_1_ignored_.AddBoolAttribute( ax::mojom::BoolAttribute::kIsLineBreakingObject, true); paragraph_1_ignored_.child_ids = {static_text_1_0_.id}; static_text_1_0_.role = ax::mojom::Role::kStaticText; // An underscore should separate words. static_text_1_0_.SetName("t_1"); paragraph_2_ignored_.role = ax::mojom::Role::kParagraph; paragraph_2_ignored_.AddState(ax::mojom::State::kIgnored); paragraph_2_ignored_.AddBoolAttribute( ax::mojom::BoolAttribute::kIsLineBreakingObject, true); paragraph_2_ignored_.child_ids = {link_2_0_ignored_.id}; link_2_0_ignored_.role = ax::mojom::Role::kLink; link_2_0_ignored_.AddState(ax::mojom::State::kLinked); link_2_0_ignored_.AddState(ax::mojom::State::kIgnored); link_2_0_ignored_.child_ids = {static_text_2_0_0_.id, static_text_2_0_1_.id}; static_text_2_0_0_.role = ax::mojom::Role::kStaticText; // A series of punctuation marks, or a stretch of whitespace should separate // words. static_text_2_0_0_.SetName("s+t++2...0. 0"); static_text_2_0_1_.role = ax::mojom::Role::kStaticText; // Both a carage return as well as a line break should separate lines, but not // a space character. static_text_2_0_1_.SetName("s t\n2\r0\r\n1"); AXTreeUpdate initial_state; initial_state.root_id = root_.id; initial_state.nodes = {root_, paragraph_0_, static_text_0_0_ignored_, paragraph_1_ignored_, static_text_1_0_, paragraph_2_ignored_, link_2_0_ignored_, static_text_2_0_0_, static_text_2_0_1_}; initial_state.has_tree_data = true; AXTreeData tree_data; tree_data.tree_id = AXTreeID::CreateNewAXTreeID(); tree_data.title = "Application"; initial_state.tree_data = tree_data; auto tree = std::make_unique(); ASSERT_TRUE(tree->Unserialize(initial_state)) << tree->error(); root_node_ = tree->root(); ASSERT_EQ(root_.id, root_node_->id()); // `SetTree` is defined in our `TestAXTreeManager` superclass and it passes // ownership of the created AXTree to the manager. SetTree(std::move(tree)); } } // namespace using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::SizeIs; using ::testing::StrEq; TEST_F(AXComputedNodeDataTest, UnignoredValues) { const AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0); const AXNode* static_text_0_0_ignored_node = paragraph_0_node->GetChildAtIndex(0); const AXNode* paragraph_1_ignored_node = root_node_->GetChildAtIndex(1); const AXNode* static_text_1_0_node = paragraph_1_ignored_node->GetChildAtIndex(0); const AXNode* paragraph_2_ignored_node = root_node_->GetChildAtIndex(2); const AXNode* link_2_0_ignored_node = paragraph_2_ignored_node->GetChildAtIndex(0); const AXNode* static_text_2_0_0_node = link_2_0_ignored_node->GetChildAtIndex(0); const AXNode* static_text_2_0_1_node = link_2_0_ignored_node->GetChildAtIndex(1); // Perform the checks twice to ensure that caching returns the same values. for (int i = 0; i < 2; ++i) { EXPECT_EQ( 0, root_node_->GetComputedNodeData().GetOrComputeUnignoredIndexInParent()); EXPECT_EQ(0, paragraph_0_node->GetComputedNodeData() .GetOrComputeUnignoredIndexInParent()); EXPECT_EQ(1, static_text_1_0_node->GetComputedNodeData() .GetOrComputeUnignoredIndexInParent()); EXPECT_EQ(2, static_text_2_0_0_node->GetComputedNodeData() .GetOrComputeUnignoredIndexInParent()); EXPECT_EQ(3, static_text_2_0_1_node->GetComputedNodeData() .GetOrComputeUnignoredIndexInParent()); EXPECT_EQ( kInvalidAXNodeID, root_node_->GetComputedNodeData().GetOrComputeUnignoredParentID()); EXPECT_EQ(nullptr, root_node_->GetComputedNodeData().GetOrComputeUnignoredParent()); EXPECT_FALSE(root_node_->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ(root_node_->id(), paragraph_0_node->GetComputedNodeData() .GetOrComputeUnignoredParentID()); EXPECT_EQ( root_node_, paragraph_0_node->GetComputedNodeData().GetOrComputeUnignoredParent()); EXPECT_FALSE(paragraph_0_node->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ(paragraph_0_node->id(), static_text_0_0_ignored_node->GetComputedNodeData() .GetOrComputeUnignoredParentID()); EXPECT_EQ(paragraph_0_node, static_text_0_0_ignored_node->GetComputedNodeData() .GetOrComputeUnignoredParent()); EXPECT_TRUE(static_text_0_0_ignored_node->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ(root_node_->id(), paragraph_1_ignored_node->GetComputedNodeData() .GetOrComputeUnignoredParentID()); EXPECT_EQ(root_node_, paragraph_1_ignored_node->GetComputedNodeData() .GetOrComputeUnignoredParent()); EXPECT_FALSE(paragraph_1_ignored_node->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ(root_node_->id(), static_text_1_0_node->GetComputedNodeData() .GetOrComputeUnignoredParentID()); EXPECT_EQ(root_node_, static_text_1_0_node->GetComputedNodeData() .GetOrComputeUnignoredParent()); EXPECT_FALSE(static_text_1_0_node->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ(root_node_->id(), paragraph_2_ignored_node->GetComputedNodeData() .GetOrComputeUnignoredParentID()); EXPECT_EQ(root_node_, paragraph_2_ignored_node->GetComputedNodeData() .GetOrComputeUnignoredParent()); EXPECT_FALSE(paragraph_2_ignored_node->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ(root_node_->id(), link_2_0_ignored_node->GetComputedNodeData() .GetOrComputeUnignoredParentID()); EXPECT_EQ(root_node_, link_2_0_ignored_node->GetComputedNodeData() .GetOrComputeUnignoredParent()); EXPECT_FALSE(link_2_0_ignored_node->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ(root_node_->id(), static_text_2_0_0_node->GetComputedNodeData() .GetOrComputeUnignoredParentID()); EXPECT_EQ(root_node_, static_text_2_0_0_node->GetComputedNodeData() .GetOrComputeUnignoredParent()); EXPECT_FALSE(static_text_2_0_0_node->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ(root_node_->id(), static_text_2_0_1_node->GetComputedNodeData() .GetOrComputeUnignoredParentID()); EXPECT_EQ(root_node_, static_text_2_0_1_node->GetComputedNodeData() .GetOrComputeUnignoredParent()); EXPECT_FALSE(static_text_2_0_1_node->GetComputedNodeData() .GetOrComputeIsDescendantOfPlatformLeaf()); EXPECT_EQ( 4, root_node_->GetComputedNodeData().GetOrComputeUnignoredChildCount()); EXPECT_EQ(0, paragraph_0_node->GetComputedNodeData() .GetOrComputeUnignoredChildCount()); EXPECT_EQ(0, static_text_1_0_node->GetComputedNodeData() .GetOrComputeUnignoredChildCount()); EXPECT_EQ(0, static_text_2_0_0_node->GetComputedNodeData() .GetOrComputeUnignoredChildCount()); EXPECT_EQ(0, static_text_2_0_1_node->GetComputedNodeData() .GetOrComputeUnignoredChildCount()); EXPECT_THAT( root_node_->GetComputedNodeData().GetOrComputeUnignoredChildIDs(), ElementsAre(paragraph_0_.id, static_text_1_0_.id, static_text_2_0_0_.id, static_text_2_0_1_.id)); } } TEST_F(AXComputedNodeDataTest, HasOrCanComputeAttribute) { EXPECT_TRUE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::StringAttribute::kValue)); EXPECT_FALSE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::StringAttribute::kHtmlTag)); EXPECT_TRUE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::IntListAttribute::kWordStarts)); EXPECT_FALSE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::IntListAttribute::kLabelledbyIds)); AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0); EXPECT_FALSE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::StringAttribute::kValue)); EXPECT_FALSE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::StringAttribute::kHtmlTag)); EXPECT_TRUE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::IntListAttribute::kWordStarts)); EXPECT_FALSE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::IntListAttribute::kLabelledbyIds)); // By removing the `ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot` // attribute, the root is no longer a content editable. root_.RemoveBoolAttribute(ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot); root_.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds, {static_text_0_0_ignored_.id}); paragraph_0_.AddStringAttribute(ax::mojom::StringAttribute::kValue, "New: \nvalue."); paragraph_0_.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "p"); paragraph_0_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts, {0, 4}); AXTreeUpdate tree_update; tree_update.root_id = root_.id; tree_update.nodes = {root_, paragraph_0_}; ASSERT_TRUE(GetTree()->Unserialize(tree_update)); root_node_ = GetTree()->root(); ASSERT_EQ(root_.id, root_node_->id()); // Computing the value attribute is only supported on non-atomic text fields. EXPECT_FALSE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::StringAttribute::kValue)); EXPECT_FALSE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::StringAttribute::kHtmlTag)); EXPECT_TRUE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::IntListAttribute::kWordStarts)); EXPECT_TRUE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::IntListAttribute::kLabelledbyIds)); paragraph_0_node = root_node_->GetChildAtIndex(0); // However, for maximum flexibility, if the value attribute is already present // in the node's data we should use it without checking if the role supports // it. EXPECT_TRUE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::StringAttribute::kValue)); EXPECT_TRUE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::StringAttribute::kHtmlTag)); EXPECT_TRUE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::IntListAttribute::kWordStarts)); EXPECT_FALSE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute( ax::mojom::IntListAttribute::kLabelledbyIds)); } TEST_F(AXComputedNodeDataTest, GetOrComputeAttribute) { // Embedded object behavior is dependant on platform. We manually set it to a // specific value so that test results are consistent across platforms. ScopedAXEmbeddedObjectBehaviorSetter embedded_object_behaviour( AXEmbeddedObjectBehavior::kSuppressCharacter); // Line breaks should be inserted between each paragraph to mirror how HTML's // "textContent" works. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttributeUTF8( ax::mojom::StringAttribute::kValue), StrEq("\nt_1\ns+t++2...0. 0s t\n2\r0\r\n1")); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttributeUTF8( ax::mojom::StringAttribute::kHtmlTag), StrEq("")); // Boundaries are delimited by a vertical bar, '|'. // Words: "|t|_|1s|+|t|++|2|...|0|. |0s| |t|\n|2|\r|0|\r\n|1|". int32_t word_starts[] = {0, 5, 8, 12, 16, 19, 21, 23, 26}; int32_t word_ends[] = {4, 6, 9, 13, 18, 20, 22, 24, 27}; EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kWordStarts), ElementsAreArray(word_starts)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kWordEnds), ElementsAreArray(word_ends)); // Lines: "|t_1s+t++2...0. 0s t|\n|2|\r|0|\r\n|1|". int32_t line_starts[] = {0, 21, 23, 26}; int32_t line_ends[] = {21, 23, 26, 27}; EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLineStarts), ElementsAreArray(line_starts)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLineEnds), ElementsAreArray(line_ends)); // Sentences: "|t_1s+t++2...0.| |0s t|\n|2|\r|0|\r\n|1|". int32_t sentence_starts[] = {0, 21, 23, 26}; int32_t sentence_ends[] = {21, 23, 26, 27}; EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kSentenceStarts), ElementsAreArray(sentence_starts)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kSentenceEnds), ElementsAreArray(sentence_ends)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLabelledbyIds), SizeIs(0)); AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttributeUTF8( ax::mojom::StringAttribute::kValue), StrEq("")) << "The static text child should be ignored."; EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttributeUTF8( ax::mojom::StringAttribute::kHtmlTag), StrEq("")); // Ignored text produces no boundaries. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kWordStarts), SizeIs(0)); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kWordEnds), SizeIs(0)); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLineStarts), SizeIs(0)); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLineEnds), SizeIs(0)); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kSentenceStarts), SizeIs(0)); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kSentenceEnds), SizeIs(0)); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLabelledbyIds), SizeIs(0)); // By removing the `ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot` // attribute, the root is no longer a content editable. root_.RemoveBoolAttribute(ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot); root_.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds, {static_text_0_0_ignored_.id}); paragraph_0_.AddStringAttribute(ax::mojom::StringAttribute::kValue, "New: \nvalue."); paragraph_0_.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "p"); // Word starts/ends are intentionally set to the wrong values to ensure that // `AXNodeData` takes priority over `AXComputedNodeData` if present. std::vector wrong_word_starts = {1, 5}; std::vector wrong_word_ends = {6, 8}; paragraph_0_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts, wrong_word_starts); paragraph_0_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds, wrong_word_ends); AXTreeUpdate tree_update; tree_update.root_id = root_.id; tree_update.nodes = {root_, paragraph_0_}; ASSERT_TRUE(GetTree()->Unserialize(tree_update)); root_node_ = GetTree()->root(); ASSERT_EQ(root_.id, root_node_->id()); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttributeUTF8( ax::mojom::StringAttribute::kValue), SizeIs(0)) << "Computing the value attribute is only supported on non-atomic text " "fields."; EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttributeUTF8( ax::mojom::StringAttribute::kHtmlTag), SizeIs(0)); // No change to the various boundaries should have been observed since the // root's text content hasn't changed. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kWordStarts), ElementsAreArray(word_starts)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kWordEnds), ElementsAreArray(word_ends)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLineStarts), ElementsAreArray(line_starts)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLineEnds), ElementsAreArray(line_ends)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kSentenceStarts), ElementsAreArray(sentence_starts)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kSentenceEnds), ElementsAreArray(sentence_ends)); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLabelledbyIds), ElementsAre(static_text_0_0_ignored_.id)); paragraph_0_node = root_node_->GetChildAtIndex(0); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttributeUTF8( ax::mojom::StringAttribute::kValue), StrEq("New: \nvalue.")) << "For maximum flexibility, if the value attribute is already present " "in the node's data we should use it without checking if the role " "supports it."; EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttributeUTF8( ax::mojom::StringAttribute::kHtmlTag), StrEq("p")); // Word starts/ends are intentionally set to the wrong values in `AXNodeData`. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kWordStarts), ElementsAreArray(wrong_word_starts)) << "`AXNodeData` should take priority over `AXComputedNodeData`, if " "present."; EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kWordEnds), ElementsAreArray(wrong_word_ends)) << "`AXNodeData` should take priority over `AXComputedNodeData`, if " "present."; EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLineStarts), ElementsAre()); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLineEnds), ElementsAre()); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kSentenceStarts), ElementsAre()); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kSentenceEnds), ElementsAre()); EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute( ax::mojom::IntListAttribute::kLabelledbyIds), SizeIs(0)); } TEST_F(AXComputedNodeDataTest, GetOrComputeTextContent) { // Embedded object behavior is dependant on platform. We manually set it to a // specific value so that test results are consistent across platforms. ScopedAXEmbeddedObjectBehaviorSetter embedded_object_behaviour( AXEmbeddedObjectBehavior::kSuppressCharacter); EXPECT_THAT(root_node_->GetComputedNodeData() .GetOrComputeTextContentWithParagraphBreaksUTF8(), StrEq("\nt_1\ns+t++2...0. 0s t\n2\r0\r\n1")); EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeTextContentUTF8(), StrEq("t_1s+t++2...0. 0s t\n2\r0\r\n1")); EXPECT_EQ( root_node_->GetComputedNodeData().GetOrComputeTextContentLengthUTF8(), 27); // Paragraph_0's text is ignored. Ignored text should not be visible. const AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0); EXPECT_THAT(paragraph_0_node->GetComputedNodeData() .GetOrComputeTextContentWithParagraphBreaksUTF8(), StrEq("")); EXPECT_THAT( paragraph_0_node->GetComputedNodeData().GetOrComputeTextContentUTF8(), StrEq("")); EXPECT_EQ(paragraph_0_node->GetComputedNodeData() .GetOrComputeTextContentLengthUTF8(), 0); // The two incarnations of the "TextContent" methods should behave identically // when line breaks are manually inserted via e.g. a
element in HTML, as // this case demonstrates. const AXNode* paragraph_2_ignored_node = root_node_->GetChildAtIndex(2); EXPECT_THAT(paragraph_2_ignored_node->GetComputedNodeData() .GetOrComputeTextContentWithParagraphBreaksUTF8(), StrEq("s+t++2...0. 0s t\n2\r0\r\n1")); EXPECT_THAT(paragraph_2_ignored_node->GetComputedNodeData() .GetOrComputeTextContentUTF8(), StrEq("s+t++2...0. 0s t\n2\r0\r\n1")); EXPECT_EQ(paragraph_2_ignored_node->GetComputedNodeData() .GetOrComputeTextContentLengthUTF8(), 24); } } // namespace ui