blob: 0b261c86c5af66f1624ca9064fd5e57d7a9fb152 [file] [log] [blame]
// Copyright 2019 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_range.h"
#include <memory>
#include <string>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_node_position.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/accessibility/test_ax_node_helper.h"
#include "ui/accessibility/test_ax_tree_manager.h"
namespace ui {
using TestPositionInstance =
std::unique_ptr<AXPosition<AXNodePosition, AXNode>>;
using TestPositionRange = AXRange<AXPosition<AXNodePosition, AXNode>>;
namespace {
constexpr AXNodeID ROOT_ID = 1;
constexpr AXNodeID DIV1_ID = 2;
constexpr AXNodeID BUTTON_ID = 3;
constexpr AXNodeID DIV2_ID = 4;
constexpr AXNodeID CHECK_BOX1_ID = 5;
constexpr AXNodeID CHECK_BOX2_ID = 6;
constexpr AXNodeID TEXT_FIELD_ID = 7;
constexpr AXNodeID STATIC_TEXT1_ID = 8;
constexpr AXNodeID INLINE_BOX1_ID = 9;
constexpr AXNodeID LINE_BREAK1_ID = 10;
constexpr AXNodeID INLINE_BOX_LINE_BREAK1_ID = 11;
constexpr AXNodeID STATIC_TEXT2_ID = 12;
constexpr AXNodeID INLINE_BOX2_ID = 13;
constexpr AXNodeID LINE_BREAK2_ID = 14;
constexpr AXNodeID INLINE_BOX_LINE_BREAK2_ID = 15;
constexpr AXNodeID PARAGRAPH_ID = 16;
constexpr AXNodeID STATIC_TEXT3_ID = 17;
constexpr AXNodeID INLINE_BOX3_ID = 18;
constexpr AXNodeID EMPTY_PARAGRAPH_ID = 19;
class TestAXRangeScreenRectDelegate : public AXRangeRectDelegate {
public:
explicit TestAXRangeScreenRectDelegate(TestAXTreeManager* tree_manager)
: tree_manager_(tree_manager) {}
virtual ~TestAXRangeScreenRectDelegate() = default;
TestAXRangeScreenRectDelegate(const TestAXRangeScreenRectDelegate& delegate) =
delete;
TestAXRangeScreenRectDelegate& operator=(
const TestAXRangeScreenRectDelegate& delegate) = delete;
gfx::Rect GetInnerTextRangeBoundsRect(
AXTreeID tree_id,
AXNodeID node_id,
int start_offset,
int end_offset,
const ui::AXClippingBehavior clipping_behavior,
AXOffscreenResult* offscreen_result) override {
if (tree_manager_->GetTreeID() != tree_id)
return gfx::Rect();
AXNode* node = tree_manager_->GetNode(node_id);
if (!node)
return gfx::Rect();
TestAXNodeHelper* wrapper =
TestAXNodeHelper::GetOrCreate(tree_manager_->GetTree(), node);
return wrapper->GetInnerTextRangeBoundsRect(
start_offset, end_offset, AXCoordinateSystem::kScreenDIPs,
clipping_behavior, offscreen_result);
}
gfx::Rect GetBoundsRect(AXTreeID tree_id,
AXNodeID node_id,
AXOffscreenResult* offscreen_result) override {
if (tree_manager_->GetTreeID() != tree_id)
return gfx::Rect();
AXNode* node = tree_manager_->GetNode(node_id);
if (!node)
return gfx::Rect();
TestAXNodeHelper* wrapper =
TestAXNodeHelper::GetOrCreate(tree_manager_->GetTree(), node);
return wrapper->GetBoundsRect(AXCoordinateSystem::kScreenDIPs,
AXClippingBehavior::kClipped,
offscreen_result);
}
private:
const raw_ptr<TestAXTreeManager> tree_manager_;
};
class AXRangeTest : public ::testing::Test, public TestAXTreeManager {
public:
const std::u16string EMPTY = u"";
const std::u16string NEWLINE = u"\n";
const std::u16string BUTTON = u"Button";
const std::u16string LINE_1 = u"Line 1";
const std::u16string LINE_2 = u"Line 2";
const std::u16string TEXT_FIELD =
LINE_1.substr().append(NEWLINE).append(LINE_2).append(NEWLINE);
const std::u16string AFTER_LINE = u"After";
const std::u16string ALL_TEXT =
BUTTON.substr().append(TEXT_FIELD).append(AFTER_LINE);
AXRangeTest();
AXRangeTest(const AXRangeTest&) = delete;
AXRangeTest& operator=(const AXRangeTest&) = delete;
~AXRangeTest() override = default;
protected:
void SetUp() override;
AXNodeData root_;
AXNodeData div1_;
AXNodeData div2_;
AXNodeData button_;
AXNodeData check_box1_;
AXNodeData check_box2_;
AXNodeData text_field_;
AXNodeData line_break1_;
AXNodeData line_break2_;
AXNodeData static_text1_;
AXNodeData static_text2_;
AXNodeData static_text3_;
AXNodeData inline_box1_;
AXNodeData inline_box2_;
AXNodeData inline_box3_;
AXNodeData inline_box_line_break1_;
AXNodeData inline_box_line_break2_;
AXNodeData paragraph_;
AXNodeData empty_paragraph_;
private:
ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior_;
};
// These tests use kSuppressCharacter behavior.
AXRangeTest::AXRangeTest()
: ax_embedded_object_behavior_(
AXEmbeddedObjectBehavior::kSuppressCharacter) {}
void AXRangeTest::SetUp() {
// Set up the AXTree for the following content:
// ++1 Role::kDialog
// ++++2 Role::kGenericContainer
// ++++++3 Role::kButton, name="Button"
// ++++++4 Role::kGenericContainer
// ++++++++5 Role::kCheckBox, name="Checkbox 1"
// ++++++++6 Role::kCheckBox, name="Checkbox 2"
// ++++7 Role::kTextField
// ++++++8 Role::kStaticText, name="Line 1"
// ++++++++9 Role::kInlineTextBox, name="Line 1"
// ++++++10 Role::kLineBreak, name="\n"
// ++++++++11 Role::kInlineTextBox, name="\n"
// ++++++12 Role::kStaticText, name="Line 2"
// ++++++++13 Role::kInlineTextBox, name="Line 2"
// ++++++14 Role::kLineBreak, name="\n"
// ++++++++15 Role::kInlineTextBox, name="\n"
// ++++16 Role::kParagraph
// ++++++17 Role::kStaticText, name="After"
// ++++++++18 Role::kInlineTextBox, name="After"
// ++++19 Role::kParagraph, IGNORED
//
// [Root]
// {0, 0, 800, 600}
//
// [Button] [Checkbox 1] [Checkbox 2]
// {20, 20, 100x30}, {120, 20, 30x30} {150, 20, 30x30}
//
// [Line 1] [\n]
// {20, 50, 30x30} {50, 50, 0x30}
//
// [Line 2] [\n]
// {20, 80, 42x30} {62, 80, 0x30}
//
// [After]
// {20, 110, 50x30}
//
// [Empty paragraph]
// {20, 140, 700, 0}
root_.id = ROOT_ID;
div1_.id = DIV1_ID;
div2_.id = DIV2_ID;
button_.id = BUTTON_ID;
check_box1_.id = CHECK_BOX1_ID;
check_box2_.id = CHECK_BOX2_ID;
text_field_.id = TEXT_FIELD_ID;
line_break1_.id = LINE_BREAK1_ID;
line_break2_.id = LINE_BREAK2_ID;
static_text1_.id = STATIC_TEXT1_ID;
static_text2_.id = STATIC_TEXT2_ID;
static_text3_.id = STATIC_TEXT3_ID;
inline_box1_.id = INLINE_BOX1_ID;
inline_box2_.id = INLINE_BOX2_ID;
inline_box3_.id = INLINE_BOX3_ID;
inline_box_line_break1_.id = INLINE_BOX_LINE_BREAK1_ID;
inline_box_line_break2_.id = INLINE_BOX_LINE_BREAK2_ID;
paragraph_.id = PARAGRAPH_ID;
empty_paragraph_.id = EMPTY_PARAGRAPH_ID;
root_.role = ax::mojom::Role::kDialog;
root_.AddState(ax::mojom::State::kFocusable);
root_.SetName(ALL_TEXT);
root_.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
div1_.role = ax::mojom::Role::kGenericContainer;
div1_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
div1_.child_ids.push_back(button_.id);
div1_.child_ids.push_back(div2_.id);
root_.child_ids.push_back(div1_.id);
button_.role = ax::mojom::Role::kButton;
button_.SetHasPopup(ax::mojom::HasPopup::kMenu);
button_.SetName("Button");
button_.SetNameFrom(ax::mojom::NameFrom::kValue);
button_.SetValue("Button");
button_.relative_bounds.bounds = gfx::RectF(20, 20, 100, 30);
button_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
check_box1_.id);
div2_.role = ax::mojom::Role::kGenericContainer;
div2_.child_ids.push_back(check_box1_.id);
div2_.child_ids.push_back(check_box2_.id);
check_box1_.role = ax::mojom::Role::kCheckBox;
check_box1_.SetCheckedState(ax::mojom::CheckedState::kTrue);
check_box1_.SetName("Checkbox 1");
check_box1_.relative_bounds.bounds = gfx::RectF(120, 20, 30, 30);
check_box1_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
button_.id);
check_box1_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
check_box2_.id);
check_box2_.role = ax::mojom::Role::kCheckBox;
check_box2_.SetCheckedState(ax::mojom::CheckedState::kTrue);
check_box2_.SetName("Checkbox 2");
check_box2_.relative_bounds.bounds = gfx::RectF(150, 20, 30, 30);
check_box2_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
check_box1_.id);
text_field_.role = ax::mojom::Role::kTextField;
text_field_.AddState(ax::mojom::State::kEditable);
text_field_.SetValue(TEXT_FIELD);
text_field_.AddIntListAttribute(ax::mojom::IntListAttribute::kLineStarts,
std::vector<int32_t>{0, 7});
text_field_.child_ids.push_back(static_text1_.id);
text_field_.child_ids.push_back(line_break1_.id);
text_field_.child_ids.push_back(static_text2_.id);
text_field_.child_ids.push_back(line_break2_.id);
root_.child_ids.push_back(text_field_.id);
static_text1_.role = ax::mojom::Role::kStaticText;
static_text1_.AddState(ax::mojom::State::kEditable);
static_text1_.SetName(LINE_1);
static_text1_.child_ids.push_back(inline_box1_.id);
inline_box1_.role = ax::mojom::Role::kInlineTextBox;
inline_box1_.AddState(ax::mojom::State::kEditable);
inline_box1_.SetName(LINE_1);
inline_box1_.relative_bounds.bounds = gfx::RectF(20, 50, 30, 30);
std::vector<int32_t> character_offsets1;
// The width of each character is 5px.
character_offsets1.push_back(25); // "L" {20, 50, 5x30}
character_offsets1.push_back(30); // "i" {25, 50, 5x30}
character_offsets1.push_back(35); // "n" {30, 50, 5x30}
character_offsets1.push_back(40); // "e" {35, 50, 5x30}
character_offsets1.push_back(45); // " " {40, 50, 5x30}
character_offsets1.push_back(50); // "1" {45, 50, 5x30}
inline_box1_.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
inline_box1_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0, 5});
inline_box1_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{4, 6});
inline_box1_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
line_break1_.id);
line_break1_.role = ax::mojom::Role::kLineBreak;
line_break1_.AddState(ax::mojom::State::kEditable);
line_break1_.SetName(NEWLINE);
line_break1_.relative_bounds.bounds = gfx::RectF(50, 50, 0, 30);
line_break1_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box1_.id);
line_break1_.child_ids.push_back(inline_box_line_break1_.id);
inline_box_line_break1_.role = ax::mojom::Role::kInlineTextBox;
inline_box_line_break1_.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
inline_box_line_break1_.SetName(NEWLINE);
inline_box_line_break1_.relative_bounds.bounds = gfx::RectF(50, 50, 0, 30);
inline_box_line_break1_.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, {0});
inline_box_line_break1_.AddIntListAttribute(
ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
inline_box_line_break1_.AddIntListAttribute(
ax::mojom::IntListAttribute::kWordEnds, std::vector<int32_t>{0});
static_text2_.role = ax::mojom::Role::kStaticText;
static_text2_.AddState(ax::mojom::State::kEditable);
static_text2_.SetName(LINE_2);
static_text2_.child_ids.push_back(inline_box2_.id);
inline_box2_.role = ax::mojom::Role::kInlineTextBox;
inline_box2_.AddState(ax::mojom::State::kEditable);
inline_box2_.SetName(LINE_2);
inline_box2_.relative_bounds.bounds = gfx::RectF(20, 80, 42, 30);
std::vector<int32_t> character_offsets2;
// The width of each character is 7 px.
character_offsets2.push_back(27); // "L" {20, 80, 7x30}
character_offsets2.push_back(34); // "i" {27, 80, 7x30}
character_offsets2.push_back(41); // "n" {34, 80, 7x30}
character_offsets2.push_back(48); // "e" {41, 80, 7x30}
character_offsets2.push_back(55); // " " {48, 80, 7x30}
character_offsets2.push_back(62); // "2" {55, 80, 7x30}
inline_box2_.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets2);
inline_box2_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0, 5});
inline_box2_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{4, 6});
inline_box2_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
line_break2_.id);
line_break2_.role = ax::mojom::Role::kLineBreak;
line_break2_.AddState(ax::mojom::State::kEditable);
line_break2_.SetName(NEWLINE);
line_break2_.relative_bounds.bounds = gfx::RectF(62, 80, 0, 30);
line_break2_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box2_.id);
line_break2_.child_ids.push_back(inline_box_line_break2_.id);
inline_box_line_break2_.role = ax::mojom::Role::kInlineTextBox;
inline_box_line_break2_.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
inline_box_line_break2_.SetName(NEWLINE);
inline_box_line_break2_.relative_bounds.bounds = gfx::RectF(62, 80, 0, 30);
inline_box_line_break2_.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, {0});
inline_box_line_break2_.AddIntListAttribute(
ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
inline_box_line_break2_.AddIntListAttribute(
ax::mojom::IntListAttribute::kWordEnds, std::vector<int32_t>{0});
paragraph_.role = ax::mojom::Role::kParagraph;
paragraph_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph_.child_ids.push_back(static_text3_.id);
root_.child_ids.push_back(paragraph_.id);
static_text3_.role = ax::mojom::Role::kStaticText;
static_text3_.SetName(AFTER_LINE);
static_text3_.child_ids.push_back(inline_box3_.id);
inline_box3_.role = ax::mojom::Role::kInlineTextBox;
inline_box3_.SetName(AFTER_LINE);
inline_box3_.relative_bounds.bounds = gfx::RectF(20, 110, 50, 30);
std::vector<int32_t> character_offsets3;
// The width of each character is 10 px.
character_offsets3.push_back(30); // "A" {20, 110, 10x30}
character_offsets3.push_back(40); // "f" {30, 110, 10x30}
character_offsets3.push_back(50); // "t" {40, 110, 10x30}
character_offsets3.push_back(60); // "e" {50, 110, 10x30}
character_offsets3.push_back(70); // "r" {60, 110, 10x30}
inline_box3_.AddIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets3);
inline_box3_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0});
inline_box3_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{5});
empty_paragraph_.role = ax::mojom::Role::kParagraph;
empty_paragraph_.AddState(ax::mojom::State::kIgnored);
empty_paragraph_.relative_bounds.bounds = gfx::RectF(20, 140, 700, 0);
root_.child_ids.push_back(empty_paragraph_.id);
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes = {root_,
div1_,
button_,
div2_,
check_box1_,
check_box2_,
text_field_,
static_text1_,
inline_box1_,
line_break1_,
inline_box_line_break1_,
static_text2_,
inline_box2_,
line_break2_,
inline_box_line_break2_,
paragraph_,
static_text3_,
inline_box3_,
empty_paragraph_};
initial_state.has_tree_data = true;
initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
initial_state.tree_data.title = "Dialog title";
SetTree(std::make_unique<AXTree>(initial_state));
}
} // namespace
TEST_F(AXRangeTest, RangeOfContents) {
const AXNode* root = GetNode(ROOT_ID);
const TestPositionRange root_range =
TestPositionRange::RangeOfContents(*root);
const AXNode* text_field = GetNode(TEXT_FIELD_ID);
const TestPositionRange text_field_range =
TestPositionRange::RangeOfContents(*text_field);
const AXNode* static_text = GetNode(STATIC_TEXT1_ID);
const TestPositionRange static_text_range =
TestPositionRange::RangeOfContents(*static_text);
const AXNode* inline_box = GetNode(INLINE_BOX1_ID);
const TestPositionRange inline_box_range =
TestPositionRange::RangeOfContents(*inline_box);
EXPECT_TRUE(root_range.anchor()->IsTreePosition());
EXPECT_EQ(root_range.anchor()->GetAnchor(), root);
EXPECT_EQ(root_range.anchor()->child_index(), 0);
EXPECT_TRUE(root_range.focus()->IsTreePosition());
EXPECT_EQ(root_range.focus()->GetAnchor(), root);
EXPECT_EQ(root_range.focus()->child_index(), 4);
EXPECT_TRUE(text_field_range.anchor()->IsTextPosition())
<< "Atomic text fields should be leaf nodes hence we get a text "
"position.";
EXPECT_EQ(text_field_range.anchor()->GetAnchor(), text_field);
EXPECT_EQ(text_field_range.anchor()->text_offset(), 0);
EXPECT_TRUE(text_field_range.focus()->IsTextPosition())
<< "Atomic text fields should be leaf nodes hence we get a text "
"position.";
EXPECT_EQ(text_field_range.focus()->GetAnchor(), text_field);
EXPECT_EQ(text_field_range.focus()->text_offset(), 14)
<< "Should be length of \"Line 1\\nLine 2\\n\".";
EXPECT_TRUE(static_text_range.anchor()->IsTextPosition());
EXPECT_EQ(static_text_range.anchor()->GetAnchor(), static_text);
EXPECT_EQ(static_text_range.anchor()->text_offset(), 0);
EXPECT_TRUE(static_text_range.focus()->IsTextPosition());
EXPECT_EQ(static_text_range.focus()->GetAnchor(), static_text);
EXPECT_EQ(static_text_range.focus()->text_offset(), 6)
<< "Should be length of \"Line 1\".";
EXPECT_TRUE(inline_box_range.anchor()->IsTextPosition());
EXPECT_EQ(inline_box_range.anchor()->GetAnchor(), inline_box);
EXPECT_EQ(inline_box_range.anchor()->text_offset(), 0);
EXPECT_TRUE(inline_box_range.focus()->IsTextPosition());
EXPECT_EQ(inline_box_range.focus()->GetAnchor(), inline_box);
EXPECT_EQ(inline_box_range.focus()->text_offset(), 6)
<< "Should be length of \"Line 1\".";
}
TEST_F(AXRangeTest, EqualityOperators) {
TestPositionInstance null_position = AXNodePosition::CreateNullPosition();
TestPositionInstance test_position1 = CreateTextPosition(
button_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance test_position2 = CreateTextPosition(
text_field_, 7 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance test_position3 = CreateTextPosition(
inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
// Invalid ranges (with at least one null endpoint).
TestPositionRange null_position_and_nullptr(null_position->Clone(), nullptr);
TestPositionRange nullptr_and_test_position(nullptr, test_position1->Clone());
TestPositionRange test_position_and_null_position(test_position2->Clone(),
null_position->Clone());
TestPositionRange test_positions_1_and_2(test_position1->Clone(),
test_position2->Clone());
TestPositionRange test_positions_2_and_1(test_position2->Clone(),
test_position1->Clone());
TestPositionRange test_positions_1_and_3(test_position1->Clone(),
test_position3->Clone());
TestPositionRange test_positions_2_and_3(test_position2->Clone(),
test_position3->Clone());
TestPositionRange test_positions_3_and_2(test_position3->Clone(),
test_position2->Clone());
EXPECT_EQ(null_position_and_nullptr, nullptr_and_test_position);
EXPECT_EQ(nullptr_and_test_position, test_position_and_null_position);
EXPECT_NE(null_position_and_nullptr, test_positions_2_and_1);
EXPECT_NE(test_positions_2_and_1, test_position_and_null_position);
EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_2);
EXPECT_NE(test_positions_2_and_1, test_positions_1_and_2);
EXPECT_EQ(test_positions_3_and_2, test_positions_2_and_3);
EXPECT_NE(test_positions_1_and_2, test_positions_2_and_3);
EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_3);
}
TEST_F(AXRangeTest, AsForwardRange) {
TestPositionRange null_range(AXNodePosition::CreateNullPosition(),
AXNodePosition::CreateNullPosition());
null_range = null_range.AsForwardRange();
EXPECT_TRUE(null_range.IsNull());
TestPositionInstance tree_position =
CreateTreePosition(button_, 0 /* child_index */);
TestPositionInstance text_position1 = CreateTextPosition(
line_break1_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance text_position2 = CreateTextPosition(
inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionRange tree_to_text_range(text_position1->Clone(),
tree_position->Clone());
tree_to_text_range = tree_to_text_range.AsForwardRange();
EXPECT_EQ(*tree_position, *tree_to_text_range.anchor());
EXPECT_EQ(*text_position1, *tree_to_text_range.focus());
TestPositionRange text_to_text_range(text_position2->Clone(),
text_position1->Clone());
text_to_text_range = text_to_text_range.AsForwardRange();
EXPECT_EQ(*text_position1, *text_to_text_range.anchor());
EXPECT_EQ(*text_position2, *text_to_text_range.focus());
}
TEST_F(AXRangeTest, IsCollapsed) {
TestPositionRange null_range(AXNodePosition::CreateNullPosition(),
AXNodePosition::CreateNullPosition());
null_range = null_range.AsForwardRange();
EXPECT_FALSE(null_range.IsCollapsed());
TestPositionInstance tree_position1 =
CreateTreePosition(text_field_, 0 /* child_index */);
// Since there are no children in inline_box1_, the following is essentially
// an "after text" position which should not compare as equivalent to the
// above tree position which is a "before text" position inside the text
// field.
TestPositionInstance tree_position2 =
CreateTreePosition(inline_box1_, 0 /* child_index */);
TestPositionInstance text_position1 = CreateTextPosition(
static_text1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance text_position2 = CreateTextPosition(
inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance text_position3 = CreateTextPosition(
inline_box2_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionRange tree_to_null_range(tree_position1->Clone(),
AXNodePosition::CreateNullPosition());
EXPECT_TRUE(tree_to_null_range.IsNull());
EXPECT_FALSE(tree_to_null_range.IsCollapsed());
TestPositionRange null_to_text_range(AXNodePosition::CreateNullPosition(),
text_position1->Clone());
EXPECT_TRUE(null_to_text_range.IsNull());
EXPECT_FALSE(null_to_text_range.IsCollapsed());
TestPositionRange tree_to_tree_range(tree_position2->Clone(),
tree_position1->Clone());
EXPECT_FALSE(tree_to_tree_range.IsCollapsed());
// A tree and a text position that essentially point to the same text offset
// are equivalent, even if they are anchored to a different node.
TestPositionRange tree_to_text_range(tree_position1->Clone(),
text_position1->Clone());
EXPECT_TRUE(tree_to_text_range.IsCollapsed());
// The following positions are not equivalent since tree_position2 is an
// "after text" position.
tree_to_text_range =
TestPositionRange(tree_position2->Clone(), text_position2->Clone());
EXPECT_FALSE(tree_to_text_range.IsCollapsed());
TestPositionRange text_to_text_range(text_position1->Clone(),
text_position1->Clone());
EXPECT_TRUE(text_to_text_range.IsCollapsed());
// Two text positions that essentially point to the same text offset are
// equivalent, even if they are anchored to a different node.
text_to_text_range =
TestPositionRange(text_position1->Clone(), text_position2->Clone());
EXPECT_TRUE(text_to_text_range.IsCollapsed());
text_to_text_range =
TestPositionRange(text_position1->Clone(), text_position3->Clone());
EXPECT_FALSE(text_to_text_range.IsCollapsed());
}
TEST_F(AXRangeTest, BeginAndEndIterators) {
TestPositionInstance null_position = AXNodePosition::CreateNullPosition();
TestPositionInstance test_position1 = CreateTextPosition(
button_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance test_position2 = CreateTextPosition(
check_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance test_position3 = CreateTextPosition(
check_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance test_position4 = CreateTextPosition(
inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionRange nullptr_and_null_position(nullptr, null_position->Clone());
EXPECT_EQ(TestPositionRange::Iterator(), nullptr_and_null_position.begin());
EXPECT_EQ(TestPositionRange::Iterator(), nullptr_and_null_position.end());
TestPositionRange test_position1_and_nullptr(test_position1->Clone(),
nullptr);
EXPECT_EQ(TestPositionRange::Iterator(), test_position1_and_nullptr.begin());
EXPECT_EQ(TestPositionRange::Iterator(), test_position1_and_nullptr.end());
TestPositionRange null_position_and_test_position2(null_position->Clone(),
test_position2->Clone());
EXPECT_EQ(TestPositionRange::Iterator(),
null_position_and_test_position2.begin());
EXPECT_EQ(TestPositionRange::Iterator(),
null_position_and_test_position2.end());
TestPositionRange test_position1_and_test_position2(test_position1->Clone(),
test_position2->Clone());
EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
test_position4->Clone()),
test_position1_and_test_position2.begin());
EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
test_position3->Clone()),
test_position1_and_test_position2.begin());
EXPECT_EQ(TestPositionRange::Iterator(test_position1->Clone(),
test_position2->Clone()),
test_position1_and_test_position2.begin());
EXPECT_EQ(TestPositionRange::Iterator(nullptr, test_position2->Clone()),
test_position1_and_test_position2.end());
TestPositionRange test_position3_and_test_position4(test_position3->Clone(),
test_position4->Clone());
EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
test_position4->Clone()),
test_position3_and_test_position4.begin());
EXPECT_NE(TestPositionRange::Iterator(test_position2->Clone(),
test_position4->Clone()),
test_position3_and_test_position4.begin());
EXPECT_EQ(TestPositionRange::Iterator(test_position3->Clone(),
test_position4->Clone()),
test_position3_and_test_position4.begin());
EXPECT_NE(TestPositionRange::Iterator(nullptr, test_position2->Clone()),
test_position3_and_test_position4.end());
EXPECT_NE(TestPositionRange::Iterator(nullptr, test_position3->Clone()),
test_position3_and_test_position4.end());
EXPECT_EQ(TestPositionRange::Iterator(nullptr, test_position4->Clone()),
test_position3_and_test_position4.end());
}
TEST_F(AXRangeTest, LeafTextRangeIteration) {
TestPositionInstance button_start = CreateTextPosition(
button_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance button_middle = CreateTextPosition(
button_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance button_end = CreateTextPosition(
button_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
// Since a check box is not visible to the text representation, it spans an
// empty anchor whose start and end positions are the same.
TestPositionInstance check_box1 = CreateTextPosition(
check_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance check_box2 = CreateTextPosition(
check_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_start = CreateTextPosition(
inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_middle = CreateTextPosition(
inline_box1_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_end = CreateTextPosition(
inline_box1_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line_break1_start =
CreateTextPosition(inline_box_line_break1_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line_break1_end =
CreateTextPosition(inline_box_line_break1_, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_start = CreateTextPosition(
inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_middle = CreateTextPosition(
inline_box2_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_end = CreateTextPosition(
inline_box2_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line_break2_start =
CreateTextPosition(inline_box_line_break2_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line_break2_end =
CreateTextPosition(inline_box_line_break2_, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance after_line_start = CreateTextPosition(
inline_box3_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance after_line_end = CreateTextPosition(
inline_box3_, 5 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance empty_paragraph_start =
CreateTextPosition(empty_paragraph_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance empty_paragraph_end =
CreateTextPosition(empty_paragraph_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
std::vector<TestPositionRange> expected_ranges;
auto TestRangeIterator =
[&expected_ranges](const TestPositionRange& test_range) {
std::vector<TestPositionRange> actual_ranges;
for (TestPositionRange leaf_text_range : test_range) {
EXPECT_TRUE(leaf_text_range.IsLeafTextRange());
actual_ranges.emplace_back(std::move(leaf_text_range));
}
EXPECT_EQ(expected_ranges.size(), actual_ranges.size());
size_t element_count =
std::min(expected_ranges.size(), actual_ranges.size());
for (size_t i = 0; i < element_count; ++i) {
EXPECT_EQ(expected_ranges[i], actual_ranges[i]);
EXPECT_EQ(expected_ranges[i].anchor()->GetAnchor(),
actual_ranges[i].anchor()->GetAnchor());
}
};
// Iterating over a null range; note that expected_ranges is empty.
TestRangeIterator(TestPositionRange(nullptr, nullptr));
TestPositionRange non_null_degenerate_range(check_box1->Clone(),
check_box1->Clone());
expected_ranges.clear();
expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
TestRangeIterator(non_null_degenerate_range);
TestPositionRange empty_text_forward_range(button_end->Clone(),
line1_start->Clone());
TestPositionRange empty_text_backward_range(line1_start->Clone(),
button_end->Clone());
expected_ranges.clear();
expected_ranges.emplace_back(button_end->Clone(), button_end->Clone());
expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
expected_ranges.emplace_back(line1_start->Clone(), line1_start->Clone());
TestRangeIterator(empty_text_forward_range);
TestRangeIterator(empty_text_backward_range);
TestPositionRange entire_anchor_forward_range(button_start->Clone(),
button_end->Clone());
TestPositionRange entire_anchor_backward_range(button_end->Clone(),
button_start->Clone());
expected_ranges.clear();
expected_ranges.emplace_back(button_start->Clone(), button_end->Clone());
TestRangeIterator(entire_anchor_forward_range);
TestRangeIterator(entire_anchor_backward_range);
TestPositionRange across_anchors_forward_range(button_middle->Clone(),
line1_middle->Clone());
TestPositionRange across_anchors_backward_range(line1_middle->Clone(),
button_middle->Clone());
expected_ranges.clear();
expected_ranges.emplace_back(button_middle->Clone(), button_end->Clone());
expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
expected_ranges.emplace_back(line1_start->Clone(), line1_middle->Clone());
TestRangeIterator(across_anchors_forward_range);
TestRangeIterator(across_anchors_backward_range);
TestPositionRange starting_at_end_position_forward_range(
line1_end->Clone(), line2_middle->Clone());
TestPositionRange starting_at_end_position_backward_range(
line2_middle->Clone(), line1_end->Clone());
expected_ranges.clear();
expected_ranges.emplace_back(line1_end->Clone(), line1_end->Clone());
expected_ranges.emplace_back(line_break1_start->Clone(),
line_break1_end->Clone());
expected_ranges.emplace_back(line2_start->Clone(), line2_middle->Clone());
TestRangeIterator(starting_at_end_position_forward_range);
TestRangeIterator(starting_at_end_position_backward_range);
TestPositionRange ending_at_start_position_forward_range(
line1_middle->Clone(), line2_start->Clone());
TestPositionRange ending_at_start_position_backward_range(
line2_start->Clone(), line1_middle->Clone());
expected_ranges.clear();
expected_ranges.emplace_back(line1_middle->Clone(), line1_end->Clone());
expected_ranges.emplace_back(line_break1_start->Clone(),
line_break1_end->Clone());
expected_ranges.emplace_back(line2_start->Clone(), line2_start->Clone());
TestRangeIterator(ending_at_start_position_forward_range);
TestRangeIterator(ending_at_start_position_backward_range);
TestPositionInstance range_start =
CreateTreePosition(root_, 0 /* child_index */);
TestPositionInstance range_end =
CreateTextPosition(root_, ALL_TEXT.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionRange entire_test_forward_range(range_start->Clone(),
range_end->Clone());
TestPositionRange entire_test_backward_range(range_end->Clone(),
range_start->Clone());
expected_ranges.clear();
expected_ranges.emplace_back(button_start->Clone(), button_end->Clone());
expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
expected_ranges.emplace_back(line1_start->Clone(), line1_end->Clone());
expected_ranges.emplace_back(line_break1_start->Clone(),
line_break1_end->Clone());
expected_ranges.emplace_back(line2_start->Clone(), line2_end->Clone());
expected_ranges.emplace_back(line_break2_start->Clone(),
line_break2_end->Clone());
expected_ranges.emplace_back(after_line_start->Clone(),
after_line_end->Clone());
expected_ranges.emplace_back(empty_paragraph_start->Clone(),
empty_paragraph_end->Clone());
TestRangeIterator(entire_test_forward_range);
TestRangeIterator(entire_test_backward_range);
}
TEST_F(AXRangeTest, GetTextWithWholeObjects) {
// Create a range starting from the button object and ending at the last
// character of the root, i.e. at the last character of the second line in the
// text field.
TestPositionInstance start = CreateTreePosition(root_, 0 /* child_index */);
TestPositionInstance end =
CreateTextPosition(root_, ALL_TEXT.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange forward_range(start->Clone(), end->Clone());
EXPECT_EQ(ALL_TEXT, forward_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange backward_range(std::move(end), std::move(start));
EXPECT_EQ(ALL_TEXT, backward_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
// Button
start = CreateTextPosition(button_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
end = CreateTextPosition(button_, BUTTON.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange button_range(start->Clone(), end->Clone());
EXPECT_EQ(BUTTON, button_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange button_range_backward(std::move(end), std::move(start));
EXPECT_EQ(BUTTON, button_range_backward.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
// text_field_
start = CreateTextPosition(text_field_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
end = CreateTextPosition(text_field_, TEXT_FIELD.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange text_field_range(start->Clone(), end->Clone());
EXPECT_EQ(TEXT_FIELD,
text_field_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange text_field_range_backward(std::move(end), std::move(start));
EXPECT_EQ(TEXT_FIELD,
text_field_range_backward.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
// static_text1_
start = CreateTextPosition(static_text1_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
end = CreateTextPosition(static_text1_, LINE_1.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange static_text1_range(start->Clone(), end->Clone());
EXPECT_EQ(LINE_1, static_text1_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange static_text1_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(LINE_1, static_text1_range_backward.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
// static_text2_
start = CreateTextPosition(static_text2_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
end = CreateTextPosition(static_text2_, LINE_2.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange static_text2_range(start->Clone(), end->Clone());
EXPECT_EQ(LINE_2, static_text2_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange static_text2_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(LINE_2, static_text2_range_backward.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
// static_text1_ to static_text2_
std::u16string text_between_text1_start_and_text2_end =
LINE_1.substr().append(NEWLINE).append(LINE_2);
start = CreateTextPosition(static_text1_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
end = CreateTextPosition(static_text2_, LINE_2.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange static_text_range(start->Clone(), end->Clone());
EXPECT_EQ(text_between_text1_start_and_text2_end,
static_text_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange static_text_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(text_between_text1_start_and_text2_end,
static_text_range_backward.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
// root_ to static_text2_'s end
std::u16string text_up_to_text2_end =
BUTTON.substr(0).append(LINE_1).append(NEWLINE).append(LINE_2);
start = CreateTreePosition(root_, 0 /* child_index */);
end = CreateTextPosition(static_text2_, LINE_2.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange root_to_static2_text_range(start->Clone(), end->Clone());
EXPECT_EQ(text_up_to_text2_end,
root_to_static2_text_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange root_to_static2_text_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(text_up_to_text2_end,
root_to_static2_text_range_backward.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
// root_ to static_text2_'s start
std::u16string text_up_to_text2_start =
BUTTON.substr(0).append(LINE_1).append(NEWLINE);
start = CreateTreePosition(root_, 0 /* child_index */);
end = CreateTreePosition(static_text2_, 0 /* child_index */);
TestPositionRange root_to_static2_tree_range(start->Clone(), end->Clone());
EXPECT_EQ(text_up_to_text2_start,
root_to_static2_tree_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange root_to_static2_tree_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(text_up_to_text2_start,
root_to_static2_tree_range_backward.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
}
TEST_F(AXRangeTest, GetTextWithTextOffsets) {
std::u16string most_text = BUTTON.substr(2).append(TEXT_FIELD.substr(0, 11));
// Create a range starting from the button object and ending two characters
// before the end of the root.
TestPositionInstance start = CreateTextPosition(
button_.id, 2 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
TestPositionInstance end = CreateTextPosition(
static_text2_, 4 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange forward_range(start->Clone(), end->Clone());
EXPECT_EQ(most_text, forward_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange backward_range(std::move(end), std::move(start));
EXPECT_EQ(most_text, backward_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
// root_ to static_text2_'s start with offsets
std::u16string text_up_to_text2_tree_start =
BUTTON.substr(0).append(TEXT_FIELD.substr(0, 10));
start = CreateTreePosition(root_, 0 /* child_index */);
end = CreateTextPosition(static_text2_, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange root_to_static2_tree_range(start->Clone(), end->Clone());
EXPECT_EQ(text_up_to_text2_tree_start,
root_to_static2_tree_range.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
TestPositionRange root_to_static2_tree_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(text_up_to_text2_tree_start,
root_to_static2_tree_range_backward.GetText(
AXTextConcatenationBehavior::kWithoutParagraphBreaks,
AXEmbeddedObjectBehavior::kSuppressCharacter));
}
TEST_F(AXRangeTest, GetTextWithEmptyRanges) {
// empty string with non-leaf tree position
TestPositionInstance start = CreateTreePosition(root_, 0 /* child_index */);
TestPositionRange non_leaf_tree_range(start->Clone(), start->Clone());
EXPECT_EQ(EMPTY, non_leaf_tree_range.GetText());
// empty string with leaf tree position
start = CreateTreePosition(inline_box1_, 0 /* child_index */);
TestPositionRange leaf_empty_range(start->Clone(), start->Clone());
EXPECT_EQ(EMPTY, leaf_empty_range.GetText());
// empty string with leaf text position and no offset
start = CreateTextPosition(inline_box1_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionRange leaf_text_no_offset(start->Clone(), start->Clone());
EXPECT_EQ(EMPTY, leaf_text_no_offset.GetText());
// empty string with leaf text position with offset
start = CreateTextPosition(inline_box1_, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionRange leaf_text_offset(start->Clone(), start->Clone());
EXPECT_EQ(EMPTY, leaf_text_offset.GetText());
// empty string with non-leaf text with no offset
start = CreateTextPosition(root_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionRange non_leaf_text_no_offset(start->Clone(), start->Clone());
EXPECT_EQ(EMPTY, non_leaf_text_no_offset.GetText());
// empty string with non-leaf text position with offset
start = CreateTextPosition(root_, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionRange non_leaf_text_offset(start->Clone(), start->Clone());
EXPECT_EQ(EMPTY, non_leaf_text_offset.GetText());
// empty string with same position between two anchors, but different offsets
TestPositionInstance after_end = CreateTextPosition(
line_break1_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance before_start = CreateTextPosition(
static_text2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionRange same_position_different_anchors_forward(
after_end->Clone(), before_start->Clone());
EXPECT_EQ(EMPTY, same_position_different_anchors_forward.GetText());
TestPositionRange same_position_different_anchors_backward(
before_start->Clone(), after_end->Clone());
EXPECT_EQ(EMPTY, same_position_different_anchors_backward.GetText());
}
TEST_F(AXRangeTest, GetTextAddingNewlineBetweenParagraphs) {
// There are three newlines between the button and the text field. The first
// two are emitted because there are two empty checkboxes following the button
// on the same line, i.e. two checkboxes without any text contents. Each empty
// object forms a paragraph boundary, so that such an object will be easily
// discernible by a screen reader user. The third newline is caused by the
// fact that the text field is on the next line.
const std::u16string button_end_to_line1_start =
NEWLINE.substr().append(NEWLINE).append(NEWLINE);
TestPositionInstance button_start = CreateTextPosition(
button_.id, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance button_end = CreateTextPosition(
button_.id, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_start = CreateTextPosition(
inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_end = CreateTextPosition(
inline_box1_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_start = CreateTextPosition(
inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_end = CreateTextPosition(
inline_box2_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance after_line_start = CreateTextPosition(
inline_box3_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance after_line_end = CreateTextPosition(
inline_box3_, 5 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
auto TestGetTextForRange = [](TestPositionInstance range_start,
TestPositionInstance range_end,
const std::u16string& expected_text,
const size_t expected_appended_newlines_count) {
TestPositionRange forward_test_range(range_start->Clone(),
range_end->Clone());
TestPositionRange backward_test_range(std::move(range_end),
std::move(range_start));
size_t appended_newlines_count = 0;
EXPECT_EQ(expected_text,
forward_test_range.GetText(
AXTextConcatenationBehavior::kWithParagraphBreaks,
g_ax_embedded_object_behavior, -1, false,
&appended_newlines_count));
EXPECT_EQ(expected_appended_newlines_count, appended_newlines_count);
EXPECT_EQ(expected_text,
backward_test_range.GetText(
AXTextConcatenationBehavior::kWithParagraphBreaks,
g_ax_embedded_object_behavior, -1, false,
&appended_newlines_count));
EXPECT_EQ(expected_appended_newlines_count, appended_newlines_count);
};
std::u16string button_start_to_line1_end =
BUTTON.substr().append(button_end_to_line1_start).append(LINE_1);
TestGetTextForRange(button_start->Clone(), line1_end->Clone(),
button_start_to_line1_end,
/* expected_appended_newlines_count */ 3);
std::u16string button_start_to_line1_start =
BUTTON.substr().append(button_end_to_line1_start);
TestGetTextForRange(button_start->Clone(), line1_start->Clone(),
button_start_to_line1_start,
/* expected_appended_newlines_count */ 3);
std::u16string button_end_to_line1_end =
button_end_to_line1_start.substr().append(LINE_1);
TestGetTextForRange(button_end->Clone(), line1_end->Clone(),
button_end_to_line1_end,
/* expected_appended_newlines_count */ 3);
TestGetTextForRange(button_end->Clone(), line1_start->Clone(),
button_end_to_line1_start,
/* expected_appended_newlines_count */ 3);
std::u16string line2_start_to_after_line_end =
LINE_2.substr().append(NEWLINE).append(AFTER_LINE);
TestGetTextForRange(line2_start->Clone(), after_line_end->Clone(),
line2_start_to_after_line_end, 0);
std::u16string line2_start_to_after_line_start =
LINE_2.substr().append(NEWLINE);
TestGetTextForRange(line2_start->Clone(), after_line_start->Clone(),
line2_start_to_after_line_start, 0);
std::u16string line2_end_to_after_line_end =
NEWLINE.substr().append(AFTER_LINE);
TestGetTextForRange(line2_end->Clone(), after_line_end->Clone(),
line2_end_to_after_line_end, 0);
std::u16string line2_end_to_after_line_start = NEWLINE;
TestGetTextForRange(line2_end->Clone(), after_line_start->Clone(),
line2_end_to_after_line_start, 0);
std::u16string all_text = BUTTON.substr()
.append(button_end_to_line1_start)
.append(TEXT_FIELD)
.append(AFTER_LINE);
TestPositionInstance start = CreateTextPosition(
root_.id, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance end =
CreateTextPosition(root_, ALL_TEXT.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestGetTextForRange(std::move(start), std::move(end), all_text,
/* expected_appended_newlines_count */ 3);
}
TEST_F(AXRangeTest, GetTextWithMaxCount) {
TestPositionInstance line1_start = CreateTextPosition(
inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_end = CreateTextPosition(
inline_box2_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionRange test_range(line1_start->Clone(), line2_end->Clone());
EXPECT_EQ(
LINE_1.substr(0, 2),
test_range.GetText(AXTextConcatenationBehavior::kWithParagraphBreaks,
g_ax_embedded_object_behavior, 2));
// Test the case where an appended newline falls right at max_count.
EXPECT_EQ(
LINE_1.substr().append(NEWLINE),
test_range.GetText(AXTextConcatenationBehavior::kWithParagraphBreaks,
g_ax_embedded_object_behavior, 7));
// Test passing -1 for max_count.
EXPECT_EQ(
LINE_1.substr().append(NEWLINE).append(LINE_2),
test_range.GetText(AXTextConcatenationBehavior::kWithParagraphBreaks,
g_ax_embedded_object_behavior, -1));
}
TEST_F(AXRangeTest, GetTextWithList) {
const std::u16string kListMarker1 = u"1. ";
const std::u16string kListItemContent = u"List item 1";
const std::u16string kListMarker2 = u"2. ";
const std::u16string kAfterList = u"After list";
const std::u16string kAllText = kListMarker1.substr()
.append(kListItemContent)
.append(NEWLINE)
.append(kListMarker2)
.append(NEWLINE)
.append(kAfterList);
// This test expects:
// "1. List item 1
// 2.
// After list"
// for the following AXTree:
// ++1 kRootWebArea
// ++++2 kList
// ++++++3 kListItem
// ++++++++4 kListMarker
// ++++++++++5 kStaticText
// ++++++++++++6 kInlineTextBox "1. "
// ++++++++7 kStaticText
// ++++++++++8 kInlineTextBox "List item 1"
// ++++++9 kListItem
// ++++++++10 kListMarker
// +++++++++++11 kStaticText
// ++++++++++++++12 kInlineTextBox "2. "
// ++++13 kStaticText
// +++++++14 kInlineTextBox "After list"
AXNodeData root;
AXNodeData list;
AXNodeData list_item1;
AXNodeData list_item2;
AXNodeData list_marker1;
AXNodeData list_marker2;
AXNodeData inline_box1;
AXNodeData inline_box2;
AXNodeData inline_box3;
AXNodeData inline_box4;
AXNodeData static_text1;
AXNodeData static_text2;
AXNodeData static_text3;
AXNodeData static_text4;
root.id = 1;
list.id = 2;
list_item1.id = 3;
list_marker1.id = 4;
static_text1.id = 5;
inline_box1.id = 6;
static_text2.id = 7;
inline_box2.id = 8;
list_item2.id = 9;
list_marker2.id = 10;
static_text3.id = 11;
inline_box3.id = 12;
static_text4.id = 13;
inline_box4.id = 14;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids = {list.id, static_text4.id};
list.role = ax::mojom::Role::kList;
list.child_ids = {list_item1.id, list_item2.id};
list_item1.role = ax::mojom::Role::kListItem;
list_item1.child_ids = {list_marker1.id, static_text2.id};
list_item1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
list_marker1.role = ax::mojom::Role::kListMarker;
list_marker1.child_ids = {static_text1.id};
static_text1.role = ax::mojom::Role::kStaticText;
static_text1.SetName(kListMarker1);
static_text1.child_ids = {inline_box1.id};
inline_box1.role = ax::mojom::Role::kInlineTextBox;
inline_box1.SetName(kListMarker1);
static_text2.role = ax::mojom::Role::kStaticText;
static_text2.SetName(kListItemContent);
static_text2.child_ids = {inline_box2.id};
inline_box2.role = ax::mojom::Role::kInlineTextBox;
inline_box2.SetName(kListItemContent);
list_item2.role = ax::mojom::Role::kListItem;
list_item2.child_ids = {list_marker2.id};
list_item2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
list_marker2.role = ax::mojom::Role::kListMarker;
list_marker2.child_ids = {static_text3.id};
static_text3.role = ax::mojom::Role::kStaticText;
static_text3.SetName(kListMarker2);
static_text3.child_ids = {inline_box3.id};
inline_box3.role = ax::mojom::Role::kInlineTextBox;
inline_box3.SetName(kListMarker2);
static_text4.role = ax::mojom::Role::kStaticText;
static_text4.SetName(kAfterList);
static_text4.child_ids = {inline_box4.id};
inline_box4.role = ax::mojom::Role::kInlineTextBox;
inline_box4.SetName(kAfterList);
AXTreeUpdate initial_state;
initial_state.root_id = root.id;
initial_state.nodes = {root, list, list_item1, list_marker1,
static_text1, inline_box1, static_text2, inline_box2,
list_item2, list_marker2, static_text3, inline_box3,
static_text4, inline_box4};
initial_state.has_tree_data = true;
initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
initial_state.tree_data.title = "Dialog title";
SetTree(std::make_unique<AXTree>(initial_state));
TestPositionInstance start = CreateTextPosition(
inline_box1, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
TestPositionInstance end = CreateTextPosition(
inline_box4, 10 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
TestPositionRange forward_range(start->Clone(), end->Clone());
EXPECT_EQ(kAllText, forward_range.GetText(
AXTextConcatenationBehavior::kWithParagraphBreaks));
TestPositionRange backward_range(std::move(end), std::move(start));
EXPECT_EQ(kAllText, backward_range.GetText(
AXTextConcatenationBehavior::kWithParagraphBreaks));
}
TEST_F(AXRangeTest, GetRects) {
TestAXRangeScreenRectDelegate delegate(this);
// Setting up ax ranges for testing.
TestPositionInstance button = CreateTextPosition(
button_.id, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance check_box1 = CreateTextPosition(
check_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance check_box2 = CreateTextPosition(
check_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_start = CreateTextPosition(
inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_second_char = CreateTextPosition(
inline_box1_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_middle = CreateTextPosition(
inline_box1_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_second_to_last_char = CreateTextPosition(
inline_box1_, 5 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_end = CreateTextPosition(
inline_box1_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_start = CreateTextPosition(
inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_second_char = CreateTextPosition(
inline_box2_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_middle = CreateTextPosition(
inline_box2_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_second_to_last_char = CreateTextPosition(
inline_box2_, 5 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_end = CreateTextPosition(
inline_box2_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance empty_paragraph_end =
CreateTextPosition(empty_paragraph_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
// Since a button is not visible to the text representation, it spans an
// empty anchor whose start and end positions are the same.
TestPositionRange button_range(button->Clone(), button->Clone());
std::vector<gfx::Rect> expected_screen_rects = {gfx::Rect(20, 20, 100, 30)};
EXPECT_THAT(button_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Since a check box is not visible to the text representation, it spans an
// empty anchor whose start and end positions are the same.
TestPositionRange check_box1_range(check_box1->Clone(), check_box1->Clone());
expected_screen_rects = {gfx::Rect(120, 20, 30, 30)};
EXPECT_THAT(check_box1_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding boxes of the button and both checkboxes.
TestPositionRange button_check_box2_range(button->Clone(),
check_box2->Clone());
expected_screen_rects = {gfx::Rect(20, 20, 100, 30),
gfx::Rect(120, 20, 30, 30),
gfx::Rect(150, 20, 30, 30)};
EXPECT_THAT(button_check_box2_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 1's degenerate range at its start.
// 0 1 2 3 4 5
// |L|i|n|e| |1|
// ||
TestPositionRange line1_degenerate_range(line1_start->Clone(),
line1_start->Clone());
expected_screen_rects = {gfx::Rect(20, 50, 1, 30)};
EXPECT_THAT(line1_degenerate_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 1, its whole range.
// 0 1 2 3 4 5
// |L|i|n|e| |1|
// |-----------|
TestPositionRange line1_whole_range(line1_start->Clone(), line1_end->Clone());
expected_screen_rects = {gfx::Rect(20, 50, 30, 30)};
EXPECT_THAT(line1_whole_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 1, its first half range.
// 0 1 2 3 4 5
// |L|i|n|e| |1|
// |-----|
TestPositionRange line1_first_half_range(line1_start->Clone(),
line1_middle->Clone());
expected_screen_rects = {gfx::Rect(20, 50, 15, 30)};
EXPECT_THAT(line1_first_half_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 1, its second half range.
// 0 1 2 3 4 5
// |L|i|n|e| |1|
// |-----|
TestPositionRange line1_second_half_range(line1_middle->Clone(),
line1_end->Clone());
expected_screen_rects = {gfx::Rect(35, 50, 15, 30)};
EXPECT_THAT(line1_second_half_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 1, its mid range.
// 0 1 2 3 4 5
// |L|i|n|e| |1|
// |-------|
TestPositionRange line1_mid_range(line1_second_char->Clone(),
line1_second_to_last_char->Clone());
expected_screen_rects = {gfx::Rect(25, 50, 20, 30)};
EXPECT_THAT(line1_mid_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 2, its whole range.
// 0 1 2 3 4 5
// |L|i|n|e| |2|
// |-----------|
TestPositionRange line2_whole_range(line2_start->Clone(), line2_end->Clone());
expected_screen_rects = {gfx::Rect(20, 80, 42, 30)};
EXPECT_THAT(line2_whole_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 2, its first half range.
// 0 1 2 3 4 5
// |L|i|n|e| |2|
// |-----|
TestPositionRange line2_first_half_range(line2_start->Clone(),
line2_middle->Clone());
expected_screen_rects = {gfx::Rect(20, 80, 21, 30)};
EXPECT_THAT(line2_first_half_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 2, its second half range.
// 0 1 2 3 4 5
// |L|i|n|e| |2|
// |-----|
TestPositionRange line2_second_half_range(line2_middle->Clone(),
line2_end->Clone());
expected_screen_rects = {gfx::Rect(41, 80, 21, 30)};
EXPECT_THAT(line2_second_half_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of text line 2, its mid range.
// 0 1 2 3 4 5
// |L|i|n|e| |2|
// |-------|
TestPositionRange line2_mid_range(line2_second_char->Clone(),
line2_second_to_last_char->Clone());
expected_screen_rects = {gfx::Rect(27, 80, 28, 30)};
EXPECT_THAT(line2_mid_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding box of degenerate range of text line 2, before its
// second character.
// 0 1 2 3 4 5
// |L|i|n|e| |2|
// ||
TestPositionRange line2_degenerate_range(line2_second_char->Clone(),
line2_second_char->Clone());
expected_screen_rects = {gfx::Rect(27, 80, 1, 30)};
EXPECT_THAT(line2_degenerate_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding boxes of text line 1 and line 2, the entire range.
// |L|i|n|e| |1|\n|L|i|n|e| |2|\n|
// |--------------------------|
TestPositionRange line1_line2_whole_range(line1_start->Clone(),
line2_end->Clone());
expected_screen_rects = {gfx::Rect(20, 50, 30, 30),
gfx::Rect(20, 80, 42, 30)};
EXPECT_THAT(line1_line2_whole_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding boxes of the range that spans from the middle of text
// line 1 to the middle of text line 2.
// |L|i|n|e| |1|\n|L|i|n|e| |2|\n|
// |--------------|
TestPositionRange line1_line2_mid_range(line1_middle->Clone(),
line2_middle->Clone());
expected_screen_rects = {gfx::Rect(35, 50, 15, 30),
gfx::Rect(20, 80, 21, 30)};
EXPECT_THAT(line1_line2_mid_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding boxes of the range that spans from the checkbox 2
// ("invisible" in the text representation) to the middle of text line 2.
// |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r<p>
// |-------------------------------|
TestPositionRange check_box2_line2_mid_range(check_box2->Clone(),
line2_middle->Clone());
expected_screen_rects = {gfx::Rect(150, 20, 30, 30),
gfx::Rect(20, 50, 30, 30),
gfx::Rect(20, 80, 21, 30)};
EXPECT_THAT(check_box2_line2_mid_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Retrieving bounding boxes of the range spanning the entire document.
// |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r<p>
// |-------------------------------------------------------------------------|
TestPositionRange entire_test_range(button->Clone(),
empty_paragraph_end->Clone());
expected_screen_rects = {
gfx::Rect(20, 20, 100, 30), gfx::Rect(120, 20, 30, 30),
gfx::Rect(150, 20, 30, 30), gfx::Rect(20, 50, 30, 30),
gfx::Rect(20, 80, 42, 30), gfx::Rect(20, 110, 50, 30)};
EXPECT_THAT(entire_test_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
}
TEST_F(AXRangeTest, GetRectsOffscreen) {
// Set up root node bounds/viewport size to {0, 50, 800x60}, so that only
// some text will be onscreen the rest will be offscreen.
AXNodeData old_root_node_data = GetRoot()->data();
AXNodeData new_root_node_data = old_root_node_data;
new_root_node_data.relative_bounds.bounds = gfx::RectF(0, 50, 800, 60);
GetRoot()->SetData(new_root_node_data);
TestAXRangeScreenRectDelegate delegate(this);
TestPositionInstance button = CreateTextPosition(
button_.id, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
TestPositionInstance empty_paragraph_end =
CreateTextPosition(empty_paragraph_, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
// [Button] [Checkbox 1] [Checkbox 2]
// {20, 20, 100x30}, {120, 20, 30x30} {150, 20, 30x30}
// ---
// [Line 1] [\n] |
// {20, 50, 30x30} | view port, onscreen
// | {0, 50, 800x60}
// [Line 2] [\n] |
// {20, 80, 42x30} |
// ---
// [After]
// {20, 110, 50x30}
//
// [Empty paragraph]
//
// Retrieving bounding boxes of the range spanning the entire document.
// |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r<P>
// |-------------------------------------------------------------------------|
TestPositionRange entire_test_range(button->Clone(),
empty_paragraph_end->Clone());
std::vector<gfx::Rect> expected_screen_rects = {gfx::Rect(20, 50, 30, 30),
gfx::Rect(20, 80, 42, 30)};
EXPECT_THAT(entire_test_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
// Reset the root node bounds/viewport size back to {0, 0, 800x600}, and
// verify all elements should be onscreen.
GetRoot()->SetData(old_root_node_data);
expected_screen_rects = {
gfx::Rect(20, 20, 100, 30), gfx::Rect(120, 20, 30, 30),
gfx::Rect(150, 20, 30, 30), gfx::Rect(20, 50, 30, 30),
gfx::Rect(20, 80, 42, 30), gfx::Rect(20, 110, 50, 30)};
EXPECT_THAT(entire_test_range.GetRects(&delegate),
::testing::ContainerEq(expected_screen_rects));
}
} // namespace ui