blob: b607bef1c5718e1e719381f1a71dbae6bd9b43ac [file] [log] [blame]
// Copyright 2016 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 <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.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_range.h"
#include "ui/accessibility/ax_serializable_tree.h"
#include "ui/accessibility/ax_text_boundary.h"
#include "ui/accessibility/ax_tree_serializer.h"
#include "ui/accessibility/ax_tree_update.h"
namespace ui {
using TestPositionType = std::unique_ptr<AXPosition<AXNodePosition, AXNode>>;
using TestPositionRange = AXRange<AXPosition<AXNodePosition, AXNode>>;
namespace {
int32_t ROOT_ID = 1;
int32_t BUTTON_ID = 2;
int32_t CHECK_BOX_ID = 3;
int32_t TEXT_FIELD_ID = 4;
int32_t STATIC_TEXT1_ID = 5;
int32_t INLINE_BOX1_ID = 6;
int32_t LINE_BREAK_ID = 7;
int32_t STATIC_TEXT2_ID = 8;
int32_t INLINE_BOX2_ID = 9;
class AXPositionTest : public testing::Test {
public:
static const char* TEXT_VALUE;
AXPositionTest() = default;
~AXPositionTest() override = default;
protected:
void SetUp() override;
void TearDown() override;
AXTree* CreateMultipageDocument(AXNodeData& root_data,
AXNodeData& page_1_data,
AXNodeData& page_1_text_data,
AXNodeData& page_2_data,
AXNodeData& page_2_text_data,
AXNodeData& page_3_data,
AXNodeData& page_3_text_data) {
AXNodePosition::SetTree(nullptr);
root_data.id = 1;
root_data.role = ax::mojom::Role::kDocument;
page_1_data.id = 2;
page_1_data.role = ax::mojom::Role::kRegion;
page_1_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsPageBreakingObject, true);
page_1_text_data.id = 3;
page_1_text_data.role = ax::mojom::Role::kStaticText;
page_1_text_data.SetName("some text on page 1");
page_1_text_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
page_1_data.child_ids = {3};
page_2_data.id = 4;
page_2_data.role = ax::mojom::Role::kRegion;
page_2_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsPageBreakingObject, true);
page_2_text_data.id = 5;
page_2_text_data.role = ax::mojom::Role::kStaticText;
page_2_text_data.SetName("some text on page 2");
page_2_text_data.AddIntAttribute(
ax::mojom::IntAttribute::kTextStyle,
static_cast<int32_t>(ax::mojom::TextStyle::kBold));
page_2_data.child_ids = {5};
page_3_data.id = 6;
page_3_data.role = ax::mojom::Role::kRegion;
page_3_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsPageBreakingObject, true);
page_3_text_data.id = 7;
page_3_text_data.role = ax::mojom::Role::kStaticText;
page_3_text_data.SetName("some more text on page 3");
page_3_data.child_ids = {7};
root_data.child_ids = {2, 4, 6};
AXTreeUpdate update;
AXTreeData tree_data;
AXTreeID new_id = AXTreeID::CreateNewAXTreeID();
tree_data.tree_id = new_id;
update.tree_data = tree_data;
update.has_tree_data = true;
update.root_id = root_data.id;
update.nodes = {root_data, page_1_data, page_1_text_data,
page_2_data, page_2_text_data, page_3_data,
page_3_text_data};
return new AXTree(update);
}
void AssertTextLengthEquals(const AXTree* tree,
int32_t node_id,
int expected_text_length) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree->data().tree_id, node_id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(expected_text_length, text_position->MaxTextOffset());
ASSERT_EQ(expected_text_length,
static_cast<int>(text_position->GetText().length()));
}
// Creates a new AXTree from a vector of nodes.
// Assumes the first node in the vector is the root.
std::unique_ptr<AXTree> CreateAXTree(const std::vector<AXNodeData>& nodes) {
AXTreeUpdate update;
AXTreeData tree_data;
tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
update.tree_data = tree_data;
update.has_tree_data = true;
update.root_id = nodes[0].id;
update.nodes = nodes;
tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
update.tree_data = tree_data;
std::unique_ptr<AXTree> tree = std::make_unique<AXTree>(update);
AXNodePosition::SetTree(tree.get());
return tree;
}
AXNodeData root_;
AXNodeData button_;
AXNodeData check_box_;
AXNodeData text_field_;
AXNodeData static_text1_;
AXNodeData line_break_;
AXNodeData static_text2_;
AXNodeData inline_box1_;
AXNodeData inline_box2_;
AXTree tree_;
DISALLOW_COPY_AND_ASSIGN(AXPositionTest);
};
// Used by AXPositionExpandToEnclosingTextBoundaryTestWithParam.
//
// Every test instance starts from a pre-determined position and calls the
// ExpandToEnclosingTextBoundary method with the arguments provided in this
// struct.
struct ExpandToEnclosingTextBoundaryTestParam {
ExpandToEnclosingTextBoundaryTestParam() = default;
// Required by GTest framework.
ExpandToEnclosingTextBoundaryTestParam(
const ExpandToEnclosingTextBoundaryTestParam& other) = default;
ExpandToEnclosingTextBoundaryTestParam& operator=(
const ExpandToEnclosingTextBoundaryTestParam& other) = default;
~ExpandToEnclosingTextBoundaryTestParam() = default;
// The text boundary to expand to.
AXTextBoundary boundary;
// Determines how to expand to the enclosing range when the starting position
// is already at a text boundary.
AXRangeExpandBehavior expand_behavior;
// The text position that should be returned for the anchor of the range.
std::string expected_anchor_position;
// The text position that should be returned for the focus of the range.
std::string expected_focus_position;
};
// This is a fixture for a set of parameterized tests that test the
// |ExpandToEnclosingTextBoundary| method with all possible input arguments.
class AXPositionExpandToEnclosingTextBoundaryTestWithParam
: public AXPositionTest,
public testing::WithParamInterface<
ExpandToEnclosingTextBoundaryTestParam> {
public:
AXPositionExpandToEnclosingTextBoundaryTestWithParam() = default;
~AXPositionExpandToEnclosingTextBoundaryTestWithParam() override = default;
DISALLOW_COPY_AND_ASSIGN(
AXPositionExpandToEnclosingTextBoundaryTestWithParam);
};
// Used by AXPositionCreatePositionAtTextBoundaryTestWithParam.
//
// Every test instance starts from a pre-determined position and calls the
// CreatePositionAtTextBoundary method with the arguments provided in this
// struct.
struct CreatePositionAtTextBoundaryTestParam {
CreatePositionAtTextBoundaryTestParam() = default;
// Required by GTest framework.
CreatePositionAtTextBoundaryTestParam(
const CreatePositionAtTextBoundaryTestParam& other) = default;
CreatePositionAtTextBoundaryTestParam& operator=(
const CreatePositionAtTextBoundaryTestParam& other) = default;
~CreatePositionAtTextBoundaryTestParam() = default;
// The text boundary to move to.
AXTextBoundary boundary;
// The direction to move to.
AXTextBoundaryDirection direction;
// What to do when the starting position is already at a text boundary, or
// when the movement operation will cause us to cross the starting object's
// boundary.
AXBoundaryBehavior boundary_behavior;
// The text position that should be returned, if the method was called on a
// text position instance.
std::string expected_text_position;
};
// This is a fixture for a set of parameterized tests that test the
// |CreatePositionAtTextBoundary| method with all possible input arguments.
class AXPositionCreatePositionAtTextBoundaryTestWithParam
: public AXPositionTest,
public testing::WithParamInterface<
CreatePositionAtTextBoundaryTestParam> {
public:
AXPositionCreatePositionAtTextBoundaryTestWithParam() = default;
~AXPositionCreatePositionAtTextBoundaryTestWithParam() override = default;
DISALLOW_COPY_AND_ASSIGN(AXPositionCreatePositionAtTextBoundaryTestWithParam);
};
// Used by |AXPositionTextNavigationTestWithParam|.
//
// The test starts from a pre-determined position and repeats a text navigation
// operation, such as |CreateNextWordStartPosition|, until it runs out of
// expectations.
struct TextNavigationTestParam {
TextNavigationTestParam() = default;
// Required by GTest framework.
TextNavigationTestParam(const TextNavigationTestParam& other) = default;
TextNavigationTestParam& operator=(const TextNavigationTestParam& other) =
default;
~TextNavigationTestParam() = default;
// Stores the method that should be called repeatedly by the test to create
// the next position.
base::RepeatingCallback<TestPositionType(const TestPositionType&)> TestMethod;
// The node at which the test should start.
AXNode::AXID start_node_id;
// The text offset at which the test should start.
int start_offset;
// A list of positions that should be returned from the method being tested,
// in stringified form.
std::vector<std::string> expectations;
};
// This is a fixture for a set of parameterized tests that ensure that text
// navigation operations, such as |CreateNextWordStartPosition|, work properly.
//
// Starting from a given position, test instances call a given text navigation
// method repeatedly and compare the return values to a set of expectations.
//
// TODO(nektar): Only text positions are tested for now.
class AXPositionTextNavigationTestWithParam
: public AXPositionTest,
public testing::WithParamInterface<TextNavigationTestParam> {
public:
AXPositionTextNavigationTestWithParam() = default;
~AXPositionTextNavigationTestWithParam() override = default;
DISALLOW_COPY_AND_ASSIGN(AXPositionTextNavigationTestWithParam);
};
const char* AXPositionTest::TEXT_VALUE = "Line 1\nLine 2";
void AXPositionTest::SetUp() {
// root_
// |
// +------------+-----------+
// | | |
// button_ check_box_ text_field_
// |
// +-----------+------------+
// | | |
// static_text1_ line_break_ static_text2_
// | |
// inline_box1_ inline_box2_
root_.id = ROOT_ID;
button_.id = BUTTON_ID;
check_box_.id = CHECK_BOX_ID;
text_field_.id = TEXT_FIELD_ID;
static_text1_.id = STATIC_TEXT1_ID;
inline_box1_.id = INLINE_BOX1_ID;
line_break_.id = LINE_BREAK_ID;
static_text2_.id = STATIC_TEXT2_ID;
inline_box2_.id = INLINE_BOX2_ID;
root_.role = ax::mojom::Role::kRootWebArea;
root_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
button_.role = ax::mojom::Role::kButton;
button_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
button_.SetHasPopup(ax::mojom::HasPopup::kMenu);
button_.SetName("Button");
button_.relative_bounds.bounds = gfx::RectF(20, 20, 200, 30);
root_.child_ids.push_back(button_.id);
check_box_.role = ax::mojom::Role::kCheckBox;
check_box_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
check_box_.SetCheckedState(ax::mojom::CheckedState::kTrue);
check_box_.SetName("Check box");
check_box_.relative_bounds.bounds = gfx::RectF(20, 50, 200, 30);
root_.child_ids.push_back(check_box_.id);
text_field_.role = ax::mojom::Role::kTextField;
text_field_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
text_field_.AddState(ax::mojom::State::kEditable);
text_field_.SetValue(TEXT_VALUE);
text_field_.AddIntListAttribute(
ax::mojom::IntListAttribute::kCachedLineStarts,
std::vector<int32_t>{0, 7});
text_field_.child_ids.push_back(static_text1_.id);
text_field_.child_ids.push_back(line_break_.id);
text_field_.child_ids.push_back(static_text2_.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);
static_text1_.AddIntAttribute(
ax::mojom::IntAttribute::kTextStyle,
static_cast<int32_t>(ax::mojom::TextStyle::kBold));
inline_box1_.role = ax::mojom::Role::kInlineTextBox;
inline_box1_.AddState(ax::mojom::State::kEditable);
inline_box1_.SetName("Line 1");
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_break_.id);
line_break_.role = ax::mojom::Role::kLineBreak;
line_break_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
line_break_.AddState(ax::mojom::State::kEditable);
line_break_.SetName("\n");
line_break_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box1_.id);
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);
static_text2_.AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, 1.0f);
inline_box2_.role = ax::mojom::Role::kInlineTextBox;
inline_box2_.AddState(ax::mojom::State::kEditable);
inline_box2_.SetName("Line 2");
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});
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.push_back(root_);
initial_state.nodes.push_back(button_);
initial_state.nodes.push_back(check_box_);
initial_state.nodes.push_back(text_field_);
initial_state.nodes.push_back(static_text1_);
initial_state.nodes.push_back(inline_box1_);
initial_state.nodes.push_back(line_break_);
initial_state.nodes.push_back(static_text2_);
initial_state.nodes.push_back(inline_box2_);
initial_state.has_tree_data = true;
initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
initial_state.tree_data.title = "Dialog title";
AXSerializableTree src_tree(initial_state);
std::unique_ptr<AXTreeSource<const AXNode*, AXNodeData, AXTreeData>>
tree_source(src_tree.CreateTreeSource());
AXTreeSerializer<const AXNode*, AXNodeData, AXTreeData> serializer(
tree_source.get());
AXTreeUpdate update;
serializer.SerializeChanges(src_tree.root(), &update);
ASSERT_TRUE(tree_.Unserialize(update));
AXNodePosition::SetTree(&tree_);
}
void AXPositionTest::TearDown() {
AXNodePosition::SetTree(nullptr);
}
} // namespace
TEST_F(AXPositionTest, Clone) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType copy_position = null_position->Clone();
ASSERT_NE(nullptr, copy_position);
EXPECT_TRUE(copy_position->IsNullPosition());
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
copy_position = tree_position->Clone();
ASSERT_NE(nullptr, copy_position);
EXPECT_TRUE(copy_position->IsTreePosition());
EXPECT_EQ(root_.id, copy_position->anchor_id());
EXPECT_EQ(1, copy_position->child_index());
EXPECT_EQ(AXNodePosition::INVALID_OFFSET, copy_position->text_offset());
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, tree_position);
copy_position = tree_position->Clone();
ASSERT_NE(nullptr, copy_position);
EXPECT_TRUE(copy_position->IsTreePosition());
EXPECT_EQ(root_.id, copy_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, copy_position->child_index());
EXPECT_EQ(AXNodePosition::INVALID_OFFSET, copy_position->text_offset());
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
copy_position = text_position->Clone();
ASSERT_NE(nullptr, copy_position);
EXPECT_TRUE(copy_position->IsTextPosition());
EXPECT_EQ(text_field_.id, copy_position->anchor_id());
EXPECT_EQ(0, copy_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, copy_position->affinity());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
copy_position = text_position->Clone();
ASSERT_NE(nullptr, copy_position);
EXPECT_TRUE(copy_position->IsTextPosition());
EXPECT_EQ(text_field_.id, copy_position->anchor_id());
EXPECT_EQ(0, copy_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, copy_position->affinity());
EXPECT_EQ(AXNodePosition::INVALID_INDEX, copy_position->child_index());
}
TEST_F(AXPositionTest, GetTextFromNullPosition) {
TestPositionType text_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsNullPosition());
ASSERT_EQ(base::WideToUTF16(L""), text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromRoot) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(base::WideToUTF16(L"Line 1\nLine 2"), text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromButton) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, button_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(base::WideToUTF16(L""), text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromCheckbox) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(base::WideToUTF16(L""), text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromTextField) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(base::WideToUTF16(L"Line 1\nLine 2"), text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromStaticText) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, static_text1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(base::WideToUTF16(L"Line 1"), text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromInlineTextBox) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(base::WideToUTF16(L"Line 1"), text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromLineBreak) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(base::WideToUTF16(L"\n"), text_position->GetText());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromNullPosition) {
TestPositionType text_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsNullPosition());
ASSERT_EQ(AXNodePosition::INVALID_INDEX, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromRoot) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(13, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromButton) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, button_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(0, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromCheckbox) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(0, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromTextfield) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(13, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromStaticText) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, static_text1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(6, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromInlineTextBox) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(6, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromLineBreak) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(1, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetUpdate) {
AXNodePosition::SetTree(nullptr);
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
AXNodeData text_data;
text_data.id = 2;
text_data.role = ax::mojom::Role::kStaticText;
text_data.SetName("some text");
AXNodeData more_text_data;
more_text_data.id = 3;
more_text_data.role = ax::mojom::Role::kStaticText;
more_text_data.SetName("more text");
root_data.child_ids = {2, 3};
std::unique_ptr<AXTree> new_tree =
CreateAXTree({root_data, text_data, more_text_data});
AssertTextLengthEquals(new_tree.get(), text_data.id, 9);
AssertTextLengthEquals(new_tree.get(), root_data.id, 18);
text_data.SetName("Adjusted line 1");
new_tree = CreateAXTree({root_data, text_data, more_text_data});
AssertTextLengthEquals(new_tree.get(), text_data.id, 15);
AssertTextLengthEquals(new_tree.get(), root_data.id, 24);
// Value should override name
text_data.SetValue("Value should override name");
new_tree = CreateAXTree({root_data, text_data, more_text_data});
AssertTextLengthEquals(new_tree.get(), text_data.id, 26);
AssertTextLengthEquals(new_tree.get(), root_data.id, 35);
// An empty value should fall back to name
text_data.SetValue("");
new_tree = CreateAXTree({root_data, text_data, more_text_data});
AssertTextLengthEquals(new_tree.get(), text_data.id, 15);
AssertTextLengthEquals(new_tree.get(), root_data.id, 24);
}
TEST_F(AXPositionTest, AtStartOfAnchorWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
EXPECT_FALSE(null_position->AtStartOfAnchor());
}
TEST_F(AXPositionTest, AtStartOfAnchorWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->AtStartOfAnchor());
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtStartOfAnchor());
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 3 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtStartOfAnchor());
// A "before text" position.
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, inline_box1_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->AtStartOfAnchor());
// An "after text" position.
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, inline_box1_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtStartOfAnchor());
}
TEST_F(AXPositionTest, AtStartOfAnchorWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfAnchor());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfAnchor());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfAnchor());
}
TEST_F(AXPositionTest, AtEndOfAnchorWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
EXPECT_FALSE(null_position->AtEndOfAnchor());
}
TEST_F(AXPositionTest, AtEndOfAnchorWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 3 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->AtEndOfAnchor());
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 2 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtEndOfAnchor());
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtEndOfAnchor());
}
TEST_F(AXPositionTest, AtEndOfAnchorWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtEndOfAnchor());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfAnchor());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfAnchor());
}
TEST_F(AXPositionTest, AtStartOfLineWithTextPosition) {
// An upstream affinity should not affect the outcome since there is no soft
// line break.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfLine());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfLine());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfLine());
// An "after text" position anchored at the line break should be equivalent to
// a "before text" position at the start of the next line.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfLine());
// An upstream affinity should not affect the outcome since there is no soft
// line break.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfLine());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfLine());
}
TEST_F(AXPositionTest, AtEndOfLineWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfLine());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtEndOfLine());
// A "before text" position anchored at the line break should visually be the
// same as a text position at the end of the previous line.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtEndOfLine());
// The following position comes after the soft line break, so it should not be
// marked as the end of the line.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfLine());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfLine());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtEndOfLine());
}
TEST_F(AXPositionTest, AtStartOfBlankLine) {
// Modify the test tree so that the line break will appear on a line of its
// own, i.e. as creating a blank line.
inline_box1_.RemoveIntAttribute(ax::mojom::IntAttribute::kNextOnLineId);
line_break_.RemoveIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId);
AXTreeUpdate update;
update.nodes = {inline_box1_, line_break_};
ASSERT_TRUE(tree_.Unserialize(update));
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, text_field_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_TRUE(tree_position->AtStartOfLine());
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfLine());
// A text position after a blank line should be equivalent to a "before text"
// position at the line that comes after it.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfLine());
}
TEST_F(AXPositionTest, AtEndOfBlankLine) {
// Modify the test tree so that the line break will appear on a line of its
// own, i.e. as creating a blank line.
inline_box1_.RemoveIntAttribute(ax::mojom::IntAttribute::kNextOnLineId);
line_break_.RemoveIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId);
AXTreeUpdate update;
update.nodes = {inline_box1_, line_break_};
ASSERT_TRUE(tree_.Unserialize(update));
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, text_field_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_FALSE(tree_position->AtEndOfLine());
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfLine());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtEndOfLine());
}
TEST_F(AXPositionTest, AtStartOfParagraphWithTextPosition) {
// An upstream affinity should not affect the outcome since there is no soft
// line break.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfParagraph());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfParagraph());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfParagraph());
// An "after text" position anchored at the line break should not be the same
// as a text position at the start of the next paragraph.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfParagraph());
// An upstream affinity should not affect the outcome since there is no soft
// line break.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfParagraph());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfParagraph());
}
TEST_F(AXPositionTest, AtEndOfParagraphWithTextPosition) {
// End of |inline_box1_| is not the end of paragraph since it's
// followed by a whitespace-only line breaking object
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// The start of |line_break_| is not the end of paragraph since it's
// not the end of its anchor.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// The end of |line_break_| is the end of paragraph since it's
// a line breaking object without additional trailing whitespace.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtEndOfParagraph());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// The end of |inline_box2_| is the end of paragraph since it's
// followed by the end of document.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtEndOfParagraph());
}
TEST_F(AXPositionTest, ParagraphEdgesWithPreservedNewLine) {
// This test updates the tree structure to test a specific edge case -
// At{Start|End}OfParagraph when an ancestor position can resolve to a
// preserved newline descendant.
// ++1 kRootWebArea isLineBreakingObject
// ++++2 kStaticText
// ++++++3 kInlineTextBox "some text"
// ++++4 kGenericContainer isLineBreakingObject
// ++++++5 kStaticText
// ++++++++6 kInlineTextBox "\n" isLineBreakingObject
// ++++++++7 kInlineTextBox "more text"
AXNodePosition::SetTree(nullptr);
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
root_data.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
AXNodeData static_text_data_1;
static_text_data_1.id = 2;
static_text_data_1.role = ax::mojom::Role::kStaticText;
static_text_data_1.SetName("some text");
AXNodeData some_text_data;
some_text_data.id = 3;
some_text_data.role = ax::mojom::Role::kInlineTextBox;
some_text_data.SetName("some text");
AXNodeData container_data;
container_data.id = 4;
container_data.role = ax::mojom::Role::kGenericContainer;
container_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData static_text_data_2;
static_text_data_2.id = 5;
static_text_data_2.role = ax::mojom::Role::kStaticText;
static_text_data_2.SetName("\nmore text");
AXNodeData preserved_newline_data;
preserved_newline_data.id = 6;
preserved_newline_data.role = ax::mojom::Role::kInlineTextBox;
preserved_newline_data.SetName("\n");
preserved_newline_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData more_text_data;
more_text_data.id = 7;
more_text_data.role = ax::mojom::Role::kInlineTextBox;
more_text_data.SetName("more text");
static_text_data_1.child_ids = {3};
container_data.child_ids = {5};
static_text_data_2.child_ids = {6, 7};
root_data.child_ids = {2, 4};
std::unique_ptr<AXTree> new_tree = CreateAXTree(
{root_data, static_text_data_1, some_text_data, container_data,
static_text_data_2, preserved_newline_data, more_text_data});
TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, root_data.id, 8 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position1->AtEndOfParagraph());
EXPECT_FALSE(text_position1->AtStartOfParagraph());
TestPositionType text_position2 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, root_data.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position2->AtEndOfParagraph());
EXPECT_FALSE(text_position2->AtStartOfParagraph());
TestPositionType text_position3 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, root_data.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
EXPECT_FALSE(text_position3->AtEndOfParagraph());
EXPECT_FALSE(text_position3->AtStartOfParagraph());
TestPositionType text_position4 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, root_data.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position4->AtEndOfParagraph());
EXPECT_TRUE(text_position4->AtStartOfParagraph());
TestPositionType text_position5 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, root_data.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
EXPECT_TRUE(text_position5->AtEndOfParagraph());
EXPECT_FALSE(text_position5->AtStartOfParagraph());
TestPositionType text_position6 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, container_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position6->AtEndOfParagraph());
EXPECT_FALSE(text_position6->AtStartOfParagraph());
TestPositionType text_position7 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, container_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position7->AtEndOfParagraph());
EXPECT_TRUE(text_position7->AtStartOfParagraph());
TestPositionType text_position8 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, container_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
EXPECT_TRUE(text_position8->AtEndOfParagraph());
EXPECT_FALSE(text_position8->AtStartOfParagraph());
TestPositionType text_position9 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, static_text_data_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position9->AtEndOfParagraph());
EXPECT_TRUE(text_position9->AtStartOfParagraph());
TestPositionType text_position10 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, static_text_data_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
EXPECT_TRUE(text_position10->AtEndOfParagraph());
EXPECT_FALSE(text_position10->AtStartOfParagraph());
TestPositionType text_position11 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, preserved_newline_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position11->AtEndOfParagraph());
EXPECT_FALSE(text_position11->AtStartOfParagraph());
TestPositionType text_position12 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, preserved_newline_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position12->AtEndOfParagraph());
EXPECT_FALSE(text_position12->AtStartOfParagraph());
TestPositionType text_position13 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, more_text_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position13->AtEndOfParagraph());
EXPECT_TRUE(text_position13->AtStartOfParagraph());
TestPositionType text_position14 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, more_text_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position14->AtEndOfParagraph());
EXPECT_FALSE(text_position14->AtStartOfParagraph());
}
TEST_F(AXPositionTest,
AtStartOrEndOfParagraphWithLeadingAndTrailingDocumentWhitespace) {
// This test updates the tree structure to test a specific edge case -
// At{Start|End}OfParagraph when an ancestor position can resolve to a
// preserved newline descendant.
// ++1 kRootWebArea isLineBreakingObject
// ++++2 kGenericContainer isLineBreakingObject
// ++++++3 kStaticText
// ++++++++4 kInlineTextBox "\n" isLineBreakingObject
// ++++5 kGenericContainer isLineBreakingObject
// ++++++6 kStaticText
// ++++++++7 kInlineTextBox "some"
// ++++++++8 kInlineTextBox " "
// ++++++++9 kInlineTextBox "text"
// ++++10 kGenericContainer isLineBreakingObject
// ++++++11 kStaticText
// ++++++++12 kInlineTextBox "\n" isLineBreakingObject
AXNodePosition::SetTree(nullptr);
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
root_data.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
AXNodeData container_data_a;
container_data_a.id = 2;
container_data_a.role = ax::mojom::Role::kGenericContainer;
container_data_a.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData static_text_data_a;
static_text_data_a.id = 3;
static_text_data_a.role = ax::mojom::Role::kStaticText;
static_text_data_a.SetName("\n");
AXNodeData inline_text_data_a;
inline_text_data_a.id = 4;
inline_text_data_a.role = ax::mojom::Role::kInlineTextBox;
inline_text_data_a.SetName("\n");
inline_text_data_a.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData container_data_b;
container_data_b.id = 5;
container_data_b.role = ax::mojom::Role::kGenericContainer;
container_data_b.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData static_text_data_b;
static_text_data_b.id = 6;
static_text_data_b.role = ax::mojom::Role::kStaticText;
static_text_data_b.SetName("some text");
AXNodeData inline_text_data_b_1;
inline_text_data_b_1.id = 7;
inline_text_data_b_1.role = ax::mojom::Role::kInlineTextBox;
inline_text_data_b_1.SetName("some");
AXNodeData inline_text_data_b_2;
inline_text_data_b_2.id = 8;
inline_text_data_b_2.role = ax::mojom::Role::kInlineTextBox;
inline_text_data_b_2.SetName(" ");
AXNodeData inline_text_data_b_3;
inline_text_data_b_3.id = 9;
inline_text_data_b_3.role = ax::mojom::Role::kInlineTextBox;
inline_text_data_b_3.SetName("text");
AXNodeData container_data_c;
container_data_c.id = 10;
container_data_c.role = ax::mojom::Role::kGenericContainer;
container_data_c.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData static_text_data_c;
static_text_data_c.id = 11;
static_text_data_c.role = ax::mojom::Role::kStaticText;
static_text_data_c.SetName("\n");
AXNodeData inline_text_data_c;
inline_text_data_c.id = 12;
inline_text_data_c.role = ax::mojom::Role::kInlineTextBox;
inline_text_data_c.SetName("\n");
inline_text_data_c.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
root_data.child_ids = {container_data_a.id, container_data_b.id,
container_data_c.id};
container_data_a.child_ids = {static_text_data_a.id};
static_text_data_a.child_ids = {inline_text_data_a.id};
container_data_b.child_ids = {static_text_data_b.id};
static_text_data_b.child_ids = {inline_text_data_b_1.id,
inline_text_data_b_2.id,
inline_text_data_b_3.id};
container_data_c.child_ids = {static_text_data_c.id};
static_text_data_c.child_ids = {inline_text_data_c.id};
std::unique_ptr<AXTree> new_tree = CreateAXTree(
{root_data, container_data_a, container_data_b, container_data_c,
static_text_data_a, static_text_data_b, static_text_data_c,
inline_text_data_a, inline_text_data_b_1, inline_text_data_b_2,
inline_text_data_b_3, inline_text_data_c});
TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_a.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position1->AtEndOfParagraph());
EXPECT_TRUE(text_position1->AtStartOfParagraph());
TestPositionType text_position2 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_a.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position2->AtEndOfParagraph());
EXPECT_FALSE(text_position2->AtStartOfParagraph());
TestPositionType text_position3 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_b_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position3->AtEndOfParagraph());
EXPECT_TRUE(text_position3->AtStartOfParagraph());
TestPositionType text_position4 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_b_1.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position4->AtEndOfParagraph());
EXPECT_FALSE(text_position4->AtStartOfParagraph());
TestPositionType text_position5 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_b_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position5->AtEndOfParagraph());
EXPECT_FALSE(text_position5->AtStartOfParagraph());
TestPositionType text_position6 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_b_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position6->AtEndOfParagraph());
EXPECT_FALSE(text_position6->AtStartOfParagraph());
TestPositionType text_position7 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_b_3.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position7->AtEndOfParagraph());
EXPECT_FALSE(text_position7->AtStartOfParagraph());
TestPositionType text_position8 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_b_3.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position8->AtEndOfParagraph());
EXPECT_FALSE(text_position8->AtStartOfParagraph());
TestPositionType text_position9 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_c.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position9->AtEndOfParagraph());
EXPECT_FALSE(text_position9->AtStartOfParagraph());
TestPositionType text_position10 = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, inline_text_data_c.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position10->AtEndOfParagraph());
EXPECT_FALSE(text_position10->AtStartOfParagraph());
}
TEST_F(AXPositionTest, LowestCommonAncestor) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
// An "after children" position.
TestPositionType root_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 3 /* child_index */);
ASSERT_NE(nullptr, root_position);
// A "before text" position.
TestPositionType button_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, button_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, button_position);
TestPositionType text_field_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, text_field_.id, 2 /* child_index */);
ASSERT_NE(nullptr, text_field_position);
TestPositionType static_text1_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, static_text1_.id, 0 /* child_index */);
ASSERT_NE(nullptr, static_text1_position);
TestPositionType static_text2_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, static_text2_.id, 0 /* child_index */);
ASSERT_NE(nullptr, static_text2_position);
TestPositionType inline_box1_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, inline_box1_position);
ASSERT_TRUE(inline_box1_position->IsTextPosition());
TestPositionType inline_box2_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, inline_box2_position);
ASSERT_TRUE(inline_box2_position->IsTextPosition());
TestPositionType test_position =
root_position->LowestCommonAncestor(*null_position.get());
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = root_position->LowestCommonAncestor(*root_position.get());
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(root_.id, test_position->anchor_id());
// The child index should be for an "after children" position, i.e. it should
// be unchanged.
EXPECT_EQ(3, test_position->child_index());
test_position =
button_position->LowestCommonAncestor(*text_field_position.get());
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(root_.id, test_position->anchor_id());
// The child index should point to the button.
EXPECT_EQ(0, test_position->child_index());
test_position =
static_text2_position->LowestCommonAncestor(*static_text1_position.get());
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
// The child index should point to the second static text node.
EXPECT_EQ(2, test_position->child_index());
test_position =
static_text1_position->LowestCommonAncestor(*text_field_position.get());
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
// The child index should point to the first static text node.
EXPECT_EQ(0, test_position->child_index());
test_position =
inline_box1_position->LowestCommonAncestor(*inline_box2_position.get());
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position =
inline_box2_position->LowestCommonAncestor(*inline_box1_position.get());
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
// The text offset should point to the second line.
EXPECT_EQ(7, test_position->text_offset());
}
TEST_F(AXPositionTest, AsTreePositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->AsTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, AsTreePositionWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->AsTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(root_.id, test_position->anchor_id());
EXPECT_EQ(1, test_position->child_index());
EXPECT_EQ(AXNodePosition::INVALID_OFFSET, test_position->text_offset());
}
TEST_F(AXPositionTest, AsTreePositionWithTextPosition) {
// Create a text position pointing to the last character in the text field.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->AsTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
// The created tree position should point to the second static text node
// inside the text field.
EXPECT_EQ(2, test_position->child_index());
// But its text offset should be unchanged.
EXPECT_EQ(12, test_position->text_offset());
// Test for a "before text" position.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
EXPECT_EQ(0, test_position->text_offset());
// Test for an "after text" position.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
EXPECT_EQ(6, test_position->text_offset());
}
TEST_F(AXPositionTest, AsTextPositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->AsTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, AsTextPositionWithTreePosition) {
// Create a tree position pointing to the line break node inside the text
// field.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, text_field_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->AsTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
// The created text position should point to the 6th character inside the text
// field, i.e. the line break.
EXPECT_EQ(6, test_position->text_offset());
// But its child index should be unchanged.
EXPECT_EQ(1, test_position->child_index());
// And the affinity cannot be anything other than downstream because we
// haven't moved up the tree and so there was no opportunity to introduce any
// ambiguity regarding the new position.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// Test for a "before text" position.
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, inline_box1_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->AsTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// Test for an "after text" position.
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, inline_box1_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->AsTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
EXPECT_EQ(0, test_position->child_index());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, AsTextPositionWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->AsTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
EXPECT_EQ(AXNodePosition::INVALID_INDEX, test_position->child_index());
}
TEST_F(AXPositionTest, AsLeafTextPositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, AsLeafTextPositionWithTreePosition) {
// Create a tree position pointing to the first static text node inside the
// text field.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, text_field_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// Create a tree position pointing to the line break node inside the text
// field.
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, text_field_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// Create a text position pointing to the second static text node inside the
// text field.
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, text_field_.id, 2 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, AsLeafTextPositionWithTextPosition) {
// Create a text position pointing to the end of the root (an "after text"
// position).
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// Even though upstream affinity doesn't make sense on a leaf node, there is
// no need to reset it to downstream.
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, test_position->affinity());
// Create a text position on the root, pointing to the line break character
// inside the text field but with an upstream affinity which will cause the
// leaf text position to be placed after the text of the first inline text
// box.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
// Even though upstream affinity doesn't make sense on a leaf node, there is
// no need to reset it to downstream.
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, test_position->affinity());
// Create a text position pointing to the line break character inside the text
// field but with an upstream affinity which will cause the leaf text position
// to be placed after the text of the first inline text box.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
// Even though upstream affinity doesn't make sense on a leaf node, there is
// no need to reset it to downstream.
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, test_position->affinity());
// Create a text position on the root, pointing to the line break character
// inside the text field.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// Create a text position pointing to the line break character inside the text
// field.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// Create a text position pointing to the offset after the last character in
// the text field, (an "after text" position).
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_field_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// Create a root text position that points to the middle of a leaf text
// position, should maintain its relative text_offset ("Lin<e> 2")
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(3, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// Create a root text position that points to the middle of a leaf text
// position, should maintain its relative text_offset ("Lin<e> 2")
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(tree_.data().tree_id, test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(3, test_position->text_offset());
// Even though upstream affinity doesn't make sense on a leaf node, there is
// no need to reset it to downstream.
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, test_position->affinity());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfAnchorWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position =
null_position->CreatePositionAtStartOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfAnchorWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position =
tree_position->CreatePositionAtStartOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(root_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(root_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// An "after text" position.
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, inline_box1_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfAnchorWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position =
text_position->CreatePositionAtStartOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreatePositionAtStartOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// Affinity should have been reset to the default value.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfAnchorWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreatePositionAtEndOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfAnchorWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 3 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->CreatePositionAtEndOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(root_.id, test_position->anchor_id());
EXPECT_EQ(3, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(root_.id, test_position->anchor_id());
EXPECT_EQ(3, test_position->child_index());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfAnchorWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->CreatePositionAtEndOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreatePositionAtEndOfAnchor();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
// Affinity should have been reset to the default value.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, CreatePositionAtPreviousFormatStartWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position =
null_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtPreviousFormatStartWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, static_text1_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position =
tree_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// AXBoundaryBehavior::CrossBoundary should return a null position when it
// reaches the start of the document.
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtPreviousFormatStartWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position =
text_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// AXBoundaryBehavior::StopIfAlreadyAtBoundary shouldn't move, since it's
// already at a boundary.
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// This time, it should be a null position, since it's the first text node
// in the document.
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtNextFormatEndWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtNextFormatEndWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, inline_box1_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// AXBoundaryBehavior::StopIfAlreadyAtBoundary shouldn't move, since it's
// already at a boundary.
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// AXBoundaryBehavior::CrossBoundary should return a null position when it
// reaches the end of the document
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtNextFormatEndWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
// AXBoundaryBehavior::StopIfAlreadyAtBoundary shouldn't move, since it's
// already at a boundary.
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
// This time, it should move due to AXBoundaryBehavior::CrossBoundary.
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(1, test_position->text_offset());
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
}
TEST_F(AXPositionTest, CreatePositionAtFormatBoundaryWithTextPosition) {
// This test updates the tree structure to test a specific edge case -
// CreatePositionAtFormatBoundary when text lies at the beginning and end
// of the AX tree.
AXNodePosition::SetTree(nullptr);
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
AXNodeData text_data;
text_data.id = 2;
text_data.role = ax::mojom::Role::kStaticText;
text_data.SetName("some text");
AXNodeData more_text_data;
more_text_data.id = 3;
more_text_data.role = ax::mojom::Role::kStaticText;
more_text_data.SetName("more text");
root_data.child_ids = {text_data.id, more_text_data.id};
std::unique_ptr<AXTree> new_tree =
CreateAXTree({root_data, text_data, more_text_data});
AXNodePosition::SetTree(new_tree.get());
// Test CreatePreviousFormatStartPosition at the start of the document.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, text_data.id, 8 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
TestPositionType test_position =
text_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// Test CreateNextFormatEndPosition at the end of the document.
text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, more_text_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(more_text_data.id, test_position->anchor_id());
EXPECT_EQ(9, test_position->text_offset());
AXNodePosition::SetTree(&tree_);
}
TEST_F(AXPositionTest, CreatePositionAtPageBoundaryWithTextPosition) {
AXNodePosition::SetTree(nullptr);
AXNodeData root_data, page_1_data, page_1_text_data, page_2_data,
page_2_text_data, page_3_data, page_3_text_data;
std::unique_ptr<AXTree> new_tree(CreateMultipageDocument(
root_data, page_1_data, page_1_text_data, page_2_data, page_2_text_data,
page_3_data, page_3_text_data));
AXNodePosition::SetTree(new_tree.get());
// Test CreateNextPageStartPosition at the start of the document.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
new_tree->data().tree_id, page_1_text_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
TestPositionType test_position = text_position->CreateNextPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_1_text_data.id, test_position->anchor_id());
EXPECT_EQ(19, test_position->text_offset());
// StopIfAlreadyAtBoundary shouldn't move at all since it's at a boundary
test_position = text_position->CreateNextPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_1_text_data.id, test_position->anchor_id());
EXPECT_EQ(19, test_position->text_offset());
// Test CreateNextPageStartPosition until the end of document is reached
test_position = text_position->CreateNextPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreateNextPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_3_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreateNextPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_3_text_data.id, test_position->anchor_id());
EXPECT_EQ(24, test_position->text_offset());
test_position = test_position->CreateNextPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_3_text_data.id, test_position->anchor_id());
EXPECT_EQ(24, test_position->text_offset());
// Moving forward past the end should return a null position
TestPositionType null_position = test_position->CreateNextPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, null_position);
EXPECT_TRUE(null_position->IsNullPosition());
null_position = test_position->CreateNextPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, null_position);
EXPECT_TRUE(null_position->IsNullPosition());
// Now move backward through the document
test_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(19, test_position->text_offset());
test_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(19, test_position->text_offset());
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_1_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(page_1_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// Moving before the start should return a null position
null_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, null_position);
EXPECT_TRUE(null_position->IsNullPosition());
null_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, null_position);
EXPECT_TRUE(null_position->IsNullPosition());
AXNodePosition::SetTree(&tree_);
}
TEST_F(AXPositionTest, CreatePositionAtPageBoundaryWithTreePosition) {
AXNodePosition::SetTree(nullptr);
AXNodeData root_data, page_1_data, page_1_text_data, page_2_data,
page_2_text_data, page_3_data, page_3_text_data;
std::unique_ptr<AXTree> new_tree(CreateMultipageDocument(
root_data, page_1_data, page_1_text_data, page_2_data, page_2_text_data,
page_3_data, page_3_text_data));
AXNodePosition::SetTree(new_tree.get());
// Test CreateNextPageStartPosition at the start of the document.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
new_tree->data().tree_id, page_1_data.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->CreateNextPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// StopIfAlreadyAtBoundary shouldn't move at all since it's at a boundary
test_position = tree_position->CreateNextPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_1_data.id, test_position->anchor_id());
EXPECT_EQ(1, test_position->child_index());
// Test CreateNextPageStartPosition until the end of document is reached
test_position = tree_position->CreateNextPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
test_position = test_position->CreateNextPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_3_text_data.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
test_position = test_position->CreateNextPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_3_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
test_position = test_position->CreateNextPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_3_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// Moving forward past the end should return a null position
TestPositionType null_position = test_position->CreateNextPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, null_position);
EXPECT_TRUE(null_position->IsNullPosition());
null_position = test_position->CreateNextPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, null_position);
EXPECT_TRUE(null_position->IsNullPosition());
// Now move backward through the document
test_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
test_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_2_text_data.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_1_text_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_1_text_data.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// Moving before the start should return a null position
null_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, null_position);
EXPECT_TRUE(null_position->IsNullPosition());
null_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, null_position);
EXPECT_TRUE(null_position->IsNullPosition());
AXNodePosition::SetTree(&tree_);
}
TEST_F(AXPositionTest, CreatePagePositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position =
null_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreateNextPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfDocumentWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position =
null_position->CreatePositionAtStartOfDocument();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePagePositionWithNonPaginatedDocument) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, static_text1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
// Non-paginated documents should move to the start of the document for
// CreatePreviousPageStartPosition (treating the entire document as a single
// page)
TestPositionType test_position =
text_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// Since there is no next page, CreateNextPageStartPosition should return a
// null position
test_position = text_position->CreateNextPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
// Since there is no previous page, CreatePreviousPageEndPosition should
// return a null position
test_position = text_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
// Since there are no distinct pages, CreateNextPageEndPosition should move
// to the end of the document, as if it's one large page.
test_position = text_position->CreateNextPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
// CreatePreviousPageStartPosition should move back to the beginning of the
// document
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// Since there's no next page, CreateNextPageStartPosition should return a
// null position
test_position = test_position->CreateNextPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
// Since there's no previous page, CreatePreviousPageEndPosition should return
// a null position
test_position = text_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
// Since there's no previous page, CreatePreviousPageStartPosition should
// return a null position
test_position = text_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfDocumentWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position =
tree_position->CreatePositionAtStartOfDocument();
EXPECT_NE(nullptr, test_position);
EXPECT_EQ(root_.id, test_position->anchor_id());
tree_position = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);