blob: 2282e1ebbb595a2f6c81e7520d4e36bb25049140 [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 <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/strings/strcat.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_tree.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/accessibility/test_ax_tree_manager.h"
namespace ui {
using TestPositionType = std::unique_ptr<AXPosition<AXNodePosition, AXNode>>;
using TestPositionRange = AXRange<AXPosition<AXNodePosition, AXNode>>;
namespace {
constexpr AXNodeID ROOT_ID = 1;
constexpr AXNodeID BUTTON_ID = 2;
constexpr AXNodeID CHECK_BOX_ID = 3;
constexpr AXNodeID TEXT_FIELD_ID = 4;
constexpr AXNodeID STATIC_TEXT1_ID = 5;
constexpr AXNodeID INLINE_BOX1_ID = 6;
constexpr AXNodeID LINE_BREAK_ID = 7;
constexpr AXNodeID STATIC_TEXT2_ID = 8;
constexpr AXNodeID INLINE_BOX2_ID = 9;
// A group of basic and extended characters.
constexpr const wchar_t* kGraphemeClusters[] = {
// The English word "hey" consisting of four ASCII characters.
L"h",
L"e",
L"y",
// A Hindi word (which means "Hindi") consisting of two Devanagari
// grapheme clusters.
L"\x0939\x093F",
L"\x0928\x094D\x0926\x0940",
// A Thai word (which means "feel") consisting of three Thai grapheme
// clusters.
L"\x0E23\x0E39\x0E49",
L"\x0E2A\x0E36",
L"\x0E01",
};
class AXPositionTest : public ::testing::Test, public TestAXTreeManager {
public:
AXPositionTest();
~AXPositionTest() override = default;
protected:
static const char* TEXT_VALUE;
void SetUp() override;
// Creates a document with three pages, adding any extra information to this
// basic document structure that has been provided as arguments.
std::unique_ptr<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) const;
// Creates a browser window with a forest of accessibility trees: A more
// complex Views tree, plus a tree for the whole webpage, containing one
// additional tree representing an out-of-process iframe. Returns a vector
// containing the three managers for the trees in an out argument. (Note that
// fatal assertions can only be propagated from a `void` method.)
void CreateBrowserWindow(AXNodeData& window,
AXNodeData& back_button,
AXNodeData& web_view,
AXNodeData& root_web_area,
AXNodeData& iframe_root,
AXNodeData& paragraph,
AXNodeData& address_bar,
std::vector<TestAXTreeManager>& out_managers) const;
// Creates a document with three static text objects each containing text in a
// different language.
std::unique_ptr<AXTree> CreateMultilingualDocument(
std::vector<int>* text_offsets) const;
void AssertTextLengthEquals(const AXTree* tree,
AXNodeID node_id,
int expected_text_length) const;
// 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,
const AXTreeID& parent_tree_id = AXTreeID()) const;
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_;
private:
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behaviour_;
// Manages a minimalistic Views tree that is hosting the test webpage.
TestAXTreeManager views_tree_manager_;
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.
ax::mojom::TextBoundary 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.
ax::mojom::TextBoundary boundary;
// The direction to move to.
ax::mojom::MoveDirection 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.
AXNodeID 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);
};
// Most tests use kSuppressCharacter behavior.
AXPositionTest::AXPositionTest()
: ax_embedded_object_behaviour_(
AXEmbeddedObjectBehavior::kSuppressCharacter) {}
const char* AXPositionTest::TEXT_VALUE = "Line 1\nLine 2";
void AXPositionTest::SetUp() {
// First create a minimalistic Views tree that would host the test webpage.
// Window (BrowserRootView)
// ++NonClientView
// ++++WebView
AXNodeData window;
window.id = 1;
window.role = ax::mojom::Role::kWindow;
window.SetName("Test page - Google Chrome");
window.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"BrowserRootView");
AXNodeData non_client_view;
non_client_view.id = 2;
non_client_view.role = ax::mojom::Role::kClient;
non_client_view.SetName("Google Chrome");
non_client_view.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"NonClientView");
window.child_ids = {non_client_view.id};
AXNodeData web_view;
web_view.id = 3;
web_view.role = ax::mojom::Role::kWebView;
web_view.AddState(ax::mojom::State::kInvisible);
web_view.SetNameExplicitlyEmpty();
web_view.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"WebView");
non_client_view.child_ids = {web_view.id};
std::unique_ptr<AXTree> views_tree =
CreateAXTree({window, non_client_view, web_view});
// Now create the webpage tree.
// 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");
// Name is not visible in the tree's text representation, i.e. it may be
// coming from an aria-label.
button_.SetNameFrom(ax::mojom::NameFrom::kAttribute);
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");
// Name is not visible in the tree's text representation, i.e. it may be
// coming from an aria-label.
check_box_.SetNameFrom(ax::mojom::NameFrom::kAttribute);
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 = {root_, button_, check_box_,
text_field_, static_text1_, inline_box1_,
line_break_, static_text2_, inline_box2_};
initial_state.has_tree_data = true;
initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
initial_state.tree_data.parent_tree_id = views_tree->GetAXTreeID();
initial_state.tree_data.title = "Dialog title";
// "SetTree" is defined in "TestAXTreeManager" and it passes ownership of the
// created AXTree to the manager.
SetTree(std::make_unique<AXTree>(initial_state));
AXTreeUpdate views_tree_update;
web_view.AddChildTreeId(GetTreeID());
views_tree_update.nodes = {web_view};
ASSERT_TRUE(views_tree->Unserialize(views_tree_update));
views_tree_manager_ = TestAXTreeManager(std::move(views_tree));
}
std::unique_ptr<AXTree> AXPositionTest::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) const {
root_data.id = 1;
root_data.role = ax::mojom::Role::kPdfRoot;
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};
return CreateAXTree({root_data, page_1_data, page_1_text_data, page_2_data,
page_2_text_data, page_3_data, page_3_text_data});
}
void AXPositionTest::CreateBrowserWindow(
AXNodeData& window,
AXNodeData& back_button,
AXNodeData& web_view,
AXNodeData& root_web_area,
AXNodeData& iframe_root,
AXNodeData& paragraph,
AXNodeData& address_bar,
std::vector<TestAXTreeManager>& out_managers) const {
// First tree: Views.
window.id = 1;
window.role = ax::mojom::Role::kWindow;
window.SetName("Test page - Google Chrome");
window.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"BrowserRootView");
AXNodeData non_client_view;
non_client_view.id = 2;
non_client_view.role = ax::mojom::Role::kClient;
non_client_view.SetName("Google Chrome");
non_client_view.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"NonClientView");
window.child_ids = {non_client_view.id};
AXNodeData browser_view;
browser_view.id = 3;
browser_view.role = ax::mojom::Role::kClient;
browser_view.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"BrowserView");
AXNodeData toolbar;
toolbar.id = 4;
toolbar.role = ax::mojom::Role::kPane;
toolbar.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"ToolbarView");
browser_view.child_ids = {toolbar.id};
back_button.id = 5;
back_button.role = ax::mojom::Role::kButton;
back_button.AddState(ax::mojom::State::kFocusable);
back_button.SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kPress);
back_button.SetHasPopup(ax::mojom::HasPopup::kMenu);
back_button.SetName("Back");
back_button.SetNameFrom(ax::mojom::NameFrom::kContents);
back_button.SetDescription("Press to go back, context menu to see history");
back_button.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"ToolbarButton");
back_button.AddAction(ax::mojom::Action::kShowContextMenu);
toolbar.child_ids = {back_button.id};
web_view.id = 6;
web_view.role = ax::mojom::Role::kWebView;
web_view.AddState(ax::mojom::State::kInvisible);
web_view.SetNameExplicitlyEmpty();
web_view.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"WebView");
address_bar.id = 7;
address_bar.role = ax::mojom::Role::kTextField;
address_bar.SetName("Address and search bar");
address_bar.SetNameFrom(ax::mojom::NameFrom::kAttribute);
address_bar.SetValue("test.com");
address_bar.AddStringAttribute(ax::mojom::StringAttribute::kAutoComplete,
"both");
address_bar.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
"OmniboxViewViews");
address_bar.AddAction(ax::mojom::Action::kShowContextMenu);
non_client_view.child_ids = {browser_view.id, web_view.id, address_bar.id};
// Second tree: webpage.
root_web_area.id = 1;
root_web_area.role = ax::mojom::Role::kRootWebArea;
root_web_area.AddState(ax::mojom::State::kFocusable);
root_web_area.SetName("Test page");
root_web_area.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData iframe;
iframe.id = 2;
iframe.role = ax::mojom::Role::kIframe;
iframe.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph.id = 3;
paragraph.role = ax::mojom::Role::kParagraph;
paragraph.SetName("After iframe");
paragraph.SetNameFrom(ax::mojom::NameFrom::kContents);
paragraph.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
root_web_area.child_ids = {iframe.id, paragraph.id};
// Third tree: out-of-process iframe.
iframe_root.id = 1;
iframe_root.role = ax::mojom::Role::kRootWebArea;
iframe_root.AddState(ax::mojom::State::kFocusable);
iframe_root.SetName("Inside iframe");
iframe_root.SetNameFrom(ax::mojom::NameFrom::kContents);
iframe_root.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
std::unique_ptr<AXTree> views_tree =
CreateAXTree({window, non_client_view, browser_view, toolbar, back_button,
web_view, address_bar});
std::unique_ptr<AXTree> webpage_tree = CreateAXTree(
{root_web_area, iframe, paragraph}, views_tree->GetAXTreeID());
std::unique_ptr<AXTree> iframe_tree =
CreateAXTree({iframe_root}, webpage_tree->GetAXTreeID());
AXTreeUpdate views_tree_update;
web_view.AddChildTreeId(webpage_tree->GetAXTreeID());
views_tree_update.nodes = {web_view};
ASSERT_TRUE(views_tree->Unserialize(views_tree_update));
AXTreeUpdate webpage_tree_update;
iframe.AddChildTreeId(iframe_tree->GetAXTreeID());
webpage_tree_update.nodes = {iframe};
ASSERT_TRUE(webpage_tree->Unserialize(webpage_tree_update));
out_managers.emplace_back(std::move(views_tree));
out_managers.emplace_back(std::move(webpage_tree));
out_managers.emplace_back(std::move(iframe_tree));
}
std::unique_ptr<AXTree> AXPositionTest::CreateMultilingualDocument(
std::vector<int>* text_offsets) const {
EXPECT_NE(nullptr, text_offsets);
text_offsets->push_back(0);
std::u16string english_text;
for (int i = 0; i < 3; ++i) {
std::u16string grapheme = base::WideToUTF16(kGraphemeClusters[i]);
EXPECT_EQ(1u, grapheme.length())
<< "All English characters should be one UTF16 code unit in length.";
text_offsets->push_back(text_offsets->back() +
static_cast<int>(grapheme.length()));
english_text.append(grapheme);
}
std::u16string hindi_text;
for (int i = 3; i < 5; ++i) {
std::u16string grapheme = base::WideToUTF16(kGraphemeClusters[i]);
EXPECT_LE(2u, grapheme.length()) << "All Hindi characters should be two "
"or more UTF16 code units in length.";
text_offsets->push_back(text_offsets->back() +
static_cast<int>(grapheme.length()));
hindi_text.append(grapheme);
}
std::u16string thai_text;
for (int i = 5; i < 8; ++i) {
std::u16string grapheme = base::WideToUTF16(kGraphemeClusters[i]);
EXPECT_LT(0u, grapheme.length())
<< "One of the Thai characters should be one UTF16 code unit, "
"whilst others should be two or more.";
text_offsets->push_back(text_offsets->back() +
static_cast<int>(grapheme.length()));
thai_text.append(grapheme);
}
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
AXNodeData text_data1;
text_data1.id = 2;
text_data1.role = ax::mojom::Role::kStaticText;
text_data1.SetName(english_text);
AXNodeData text_data2;
text_data2.id = 3;
text_data2.role = ax::mojom::Role::kStaticText;
text_data2.SetName(hindi_text);
AXNodeData text_data3;
text_data3.id = 4;
text_data3.role = ax::mojom::Role::kStaticText;
text_data3.SetName(thai_text);
root_data.child_ids = {text_data1.id, text_data2.id, text_data3.id};
return CreateAXTree({root_data, text_data1, text_data2, text_data3});
}
void AXPositionTest::AssertTextLengthEquals(const AXTree* tree,
AXNodeID node_id,
int expected_text_length) const {
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()));
}
std::unique_ptr<AXTree> AXPositionTest::CreateAXTree(
const std::vector<AXNodeData>& nodes,
const AXTreeID& parent_tree_id) const {
EXPECT_FALSE(nodes.empty());
AXTreeUpdate update;
update.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
update.tree_data.parent_tree_id = parent_tree_id;
update.has_tree_data = true;
update.root_id = nodes[0].id;
update.nodes = nodes;
return std::make_unique<AXTree>(update);
}
} // 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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, Serialize) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType copy_position =
AXNodePosition::Unserialize(null_position->Serialize());
ASSERT_NE(nullptr, copy_position);
EXPECT_TRUE(copy_position->IsNullPosition());
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
copy_position = AXNodePosition::Unserialize(tree_position->Serialize());
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(
GetTreeID(), root_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, tree_position);
copy_position = AXNodePosition::Unserialize(tree_position->Serialize());
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(
GetTreeID(), text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
copy_position = AXNodePosition::Unserialize(text_position->Serialize());
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(
GetTreeID(), text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
copy_position = AXNodePosition::Unserialize(text_position->Serialize());
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, ToString) {
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
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 static_text_data_2;
static_text_data_2.id = 3;
static_text_data_2.role = ax::mojom::Role::kStaticText;
static_text_data_2.SetName(u"\xfffc");
AXNodeData static_text_data_3;
static_text_data_3.id = 4;
static_text_data_3.role = ax::mojom::Role::kStaticText;
static_text_data_3.SetName("more text");
root_data.child_ids = {static_text_data_1.id, static_text_data_2.id,
static_text_data_3.id};
SetTree(CreateAXTree(
{root_data, static_text_data_1, static_text_data_2, static_text_data_3}));
TestPositionType text_position_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_1->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=1 text_offset=0 affinity=downstream "
"annotated_text=<s>ome text\xEF\xBF\xBCmore text",
text_position_1->ToString());
TestPositionType text_position_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_2->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=1 text_offset=5 affinity=downstream "
"annotated_text=some <t>ext\xEF\xBF\xBCmore text",
text_position_2->ToString());
TestPositionType text_position_3 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_3->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=1 text_offset=9 affinity=downstream "
"annotated_text=some text<\xEF\xBF\xBC>more text",
text_position_3->ToString());
TestPositionType text_position_4 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_4->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=1 text_offset=10 affinity=downstream "
"annotated_text=some text\xEF\xBF\xBC<m>ore text",
text_position_4->ToString());
TestPositionType text_position_5 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 19 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_5->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=1 text_offset=19 affinity=downstream "
"annotated_text=some text\xEF\xBF\xBCmore text<>",
text_position_5->ToString());
TestPositionType text_position_6 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_data_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_6->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=3 text_offset=0 affinity=downstream "
"annotated_text=<\xEF\xBF\xBC>",
text_position_6->ToString());
TestPositionType text_position_7 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_data_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_7->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=3 text_offset=1 affinity=downstream "
"annotated_text=\xEF\xBF\xBC<>",
text_position_7->ToString());
TestPositionType text_position_8 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_data_3.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_8->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<m>ore text",
text_position_8->ToString());
TestPositionType text_position_9 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_data_3.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_9->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=4 text_offset=5 affinity=downstream "
"annotated_text=more <t>ext",
text_position_9->ToString());
TestPositionType text_position_10 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_data_3.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_10->IsTextPosition());
EXPECT_EQ(
"TextPosition anchor_id=4 text_offset=9 affinity=downstream "
"annotated_text=more text<>",
text_position_10->ToString());
}
TEST_F(AXPositionTest, IsIgnored) {
EXPECT_FALSE(AXNodePosition::CreateNullPosition()->IsIgnored());
// We now need to update the tree structure to test ignored tree and text
// positions.
//
// ++root_data
// ++++static_text_data_1 "One" ignored
// ++++++inline_box_data_1 "One" ignored
// ++++container_data ignored
// ++++++static_text_data_2 "Two"
// ++++++++inline_box_data_2 "Two"
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
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("One");
static_text_data_1.AddState(ax::mojom::State::kIgnored);
AXNodeData inline_box_data_1;
inline_box_data_1.id = 3;
inline_box_data_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_1.SetName("One");
inline_box_data_1.AddState(ax::mojom::State::kIgnored);
AXNodeData container_data;
container_data.id = 4;
container_data.role = ax::mojom::Role::kGenericContainer;
container_data.AddState(ax::mojom::State::kIgnored);
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("Two");
AXNodeData inline_box_data_2;
inline_box_data_2.id = 6;
inline_box_data_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_2.SetName("Two");
static_text_data_1.child_ids = {inline_box_data_1.id};
container_data.child_ids = {static_text_data_2.id};
static_text_data_2.child_ids = {inline_box_data_2.id};
root_data.child_ids = {static_text_data_1.id, container_data.id};
SetTree(
CreateAXTree({root_data, static_text_data_1, inline_box_data_1,
container_data, static_text_data_2, inline_box_data_2}));
//
// Text positions.
//
// A "before text" position on the root should not be ignored, despite the
// fact that the leaf equivalent position is, because AXPosition always
// adjusts to an unignored position if asked to find the leaf equivalent
// position. In other words, the text of ignored leaves is not propagated to
// the inner text of their ancestors.
// Create a text position before the letter "T" in "Two".
TestPositionType text_position_3 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_3->IsTextPosition());
// Since the leaf node containing the text that is pointed to is not ignored,
// but only a generic container that is in between this position and the leaf
// node, this position should not be ignored.
EXPECT_FALSE(text_position_3->IsIgnored());
// Create a text position before the letter "w" in "Two".
TestPositionType text_position_4 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_4->IsTextPosition());
// Same as above.
EXPECT_FALSE(text_position_4->IsIgnored());
// But a text position on the ignored generic container itself, should be
// ignored.
TestPositionType text_position_5 = AXNodePosition::CreateTextPosition(
GetTreeID(), container_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_5->IsTextPosition());
EXPECT_TRUE(text_position_5->IsIgnored());
// Whilst a text position on its static text child should not be ignored since
// there is nothing ignored below the generic container.
TestPositionType text_position_6 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_data_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_6->IsTextPosition());
EXPECT_FALSE(text_position_6->IsIgnored());
// A text position on an ignored leaf node should be ignored.
TestPositionType text_position_7 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_data_1.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position_7->IsTextPosition());
EXPECT_TRUE(text_position_7->IsIgnored());
//
// Tree positions.
//
// A "before children" position on the root should be ignored because the
// first child of the root is ignored.
TestPositionType tree_position_1 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_data.id, 0 /* child_index */);
ASSERT_TRUE(tree_position_1->IsTreePosition());
EXPECT_TRUE(tree_position_1->IsIgnored());
// A tree position pointing to an ignored child node should be ignored.
TestPositionType tree_position_2 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_data.id, 1 /* child_index */);
ASSERT_TRUE(tree_position_2->IsTreePosition());
EXPECT_TRUE(tree_position_2->IsIgnored());
// An "after text" tree position on an ignored leaf node should be ignored.
TestPositionType tree_position_3 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_data_1.id, 0 /* child_index */);
ASSERT_TRUE(tree_position_3->IsTreePosition());
EXPECT_TRUE(tree_position_3->IsIgnored());
// A "before text" tree position on an ignored leaf node should be ignored.
TestPositionType tree_position_4 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_data_1.id, AXNodePosition::BEFORE_TEXT);
ASSERT_TRUE(tree_position_4->IsTreePosition());
EXPECT_TRUE(tree_position_4->IsIgnored());
// An "after children" tree position on the root node, where the last child is
// ignored, should be ignored.
TestPositionType tree_position_5 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_data.id, 2 /* child_index */);
ASSERT_TRUE(tree_position_5->IsTreePosition());
EXPECT_TRUE(tree_position_5->IsIgnored());
// A "before text" position on an ignored node should be ignored.
TestPositionType tree_position_6 = AXNodePosition::CreateTreePosition(
GetTreeID(), static_text_data_1.id, AXNodePosition::BEFORE_TEXT);
ASSERT_TRUE(tree_position_6->IsTreePosition());
EXPECT_TRUE(tree_position_6->IsIgnored());
}
TEST_F(AXPositionTest, GetTextFromNullPosition) {
TestPositionType text_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsNullPosition());
ASSERT_EQ(u"", text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromRoot) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(u"Line 1\nLine 2", text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromButton) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), button_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(u"", text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromCheckbox) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(u"", text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromTextField) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(u"Line 1\nLine 2", text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromStaticText) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(u"Line 1", text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromInlineTextBox) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(u"Line 1", text_position->GetText());
}
TEST_F(AXPositionTest, GetTextFromLineBreak) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(u"\n", text_position->GetText());
}
TEST_F(AXPositionTest, IsInLineBreak) {
// "Line <1>".
TestPositionType text_field_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_field_position);
EXPECT_FALSE(text_field_position->IsPointingToLineBreak());
// "Line 1<>".
TestPositionType static_text1_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, static_text1_position);
EXPECT_FALSE(static_text1_position->IsPointingToLineBreak());
// Before the line break.
text_field_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_field_position);
EXPECT_TRUE(text_field_position->IsPointingToLineBreak());
// After the line break.
text_field_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 7 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_field_position);
EXPECT_FALSE(text_field_position->IsPointingToLineBreak());
text_field_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 7 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_field_position);
EXPECT_TRUE(text_field_position->IsPointingToLineBreak());
TestPositionType line_break_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, line_break_position);
EXPECT_TRUE(line_break_position->IsPointingToLineBreak());
line_break_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, line_break_position);
EXPECT_TRUE(line_break_position->IsPointingToLineBreak());
// An upstream affinity should not matter on leaf nodes.
line_break_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, line_break_position);
EXPECT_TRUE(line_break_position->IsPointingToLineBreak());
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
AXNodeData text_field_data;
text_field_data.id = 2;
text_field_data.role = ax::mojom::Role::kTextField;
text_field_data.SetValue(" \n");
root_data.child_ids = {text_field_data.id};
SetTree(CreateAXTree({root_data, text_field_data}));
TestPositionType root_data_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, root_data_position);
EXPECT_FALSE(root_data_position->IsPointingToLineBreak());
root_data_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, root_data_position);
EXPECT_TRUE(root_data_position->IsPointingToLineBreak());
root_data_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, root_data_position);
EXPECT_FALSE(root_data_position->IsPointingToLineBreak());
}
TEST_F(AXPositionTest, IsInWhiteSpace) {
// Before the line break. Even though the leaf equivalent position is inside
// the line break which is certainly whitespace, the whole of the text field
// does not only have whitespace in it.
TestPositionType text_field_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_field_position);
EXPECT_FALSE(text_field_position->IsInWhiteSpace());
TestPositionType line_break_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, line_break_position);
EXPECT_TRUE(line_break_position->IsInWhiteSpace());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromNullPosition) {
TestPositionType text_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsNullPosition());
ASSERT_EQ(AXNodePosition::INVALID_OFFSET, text_position->MaxTextOffset());
}
TEST_F(AXPositionTest, GetMaxTextOffsetFromRoot) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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) {
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
AXNodeData text_field_data;
text_field_data.id = 2;
text_field_data.role = ax::mojom::Role::kTextField;
text_field_data.SetName("some text");
text_field_data.SetNameFrom(ax::mojom::NameFrom::kPlaceholder);
AXNodeData text_data;
text_data.id = 3;
text_data.role = ax::mojom::Role::kStaticText;
text_data.SetName("more text");
text_data.SetNameFrom(ax::mojom::NameFrom::kContents);
root_data.child_ids = {text_field_data.id, text_data.id};
SetTree(CreateAXTree({root_data, text_field_data, text_data}));
AssertTextLengthEquals(GetTree(), text_field_data.id, 9);
AssertTextLengthEquals(GetTree(), text_data.id, 9);
AssertTextLengthEquals(GetTree(), root_data.id, 18);
// Update the placeholder text.
text_field_data.SetName("Adjusted line 1");
SetTree(CreateAXTree({root_data, text_field_data, text_data}));
AssertTextLengthEquals(GetTree(), text_field_data.id, 15);
AssertTextLengthEquals(GetTree(), text_data.id, 9);
AssertTextLengthEquals(GetTree(), root_data.id, 24);
// Value should override name in text fields.
text_field_data.SetValue("Value should override name");
SetTree(CreateAXTree({root_data, text_field_data, text_data}));
AssertTextLengthEquals(GetTree(), text_field_data.id, 26);
AssertTextLengthEquals(GetTree(), text_data.id, 9);
AssertTextLengthEquals(GetTree(), root_data.id, 35);
// An empty value should fall back to placeholder text.
text_field_data.SetValue("");
SetTree(CreateAXTree({root_data, text_field_data, text_data}));
AssertTextLengthEquals(GetTree(), text_field_data.id, 15);
AssertTextLengthEquals(GetTree(), text_data.id, 9);
AssertTextLengthEquals(GetTree(), root_data.id, 24);
}
TEST_F(AXPositionTest, GetMaxTextOffsetAndGetTextWithGeneratedContent) {
// ++1 kRootWebArea
// ++++2 kTextField
// ++++++3 kStaticText
// ++++++++4 kInlineTextBox
// ++++++5 kStaticText
// ++++++++6 kInlineTextBox
AXNodeData root_1;
AXNodeData text_field_2;
AXNodeData static_text_3;
AXNodeData inline_box_4;
AXNodeData static_text_5;
AXNodeData inline_box_6;
root_1.id = 1;
text_field_2.id = 2;
static_text_3.id = 3;
inline_box_4.id = 4;
static_text_5.id = 5;
inline_box_6.id = 6;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {text_field_2.id};
text_field_2.role = ax::mojom::Role::kTextField;
text_field_2.SetValue("3.14");
text_field_2.child_ids = {static_text_3.id, static_text_5.id};
static_text_3.role = ax::mojom::Role::kStaticText;
static_text_3.SetName("Placeholder from generated content");
static_text_3.child_ids = {inline_box_4.id};
inline_box_4.role = ax::mojom::Role::kInlineTextBox;
inline_box_4.SetName("Placeholder from generated content");
static_text_5.role = ax::mojom::Role::kStaticText;
static_text_5.SetName("3.14");
static_text_5.child_ids = {inline_box_6.id};
inline_box_6.role = ax::mojom::Role::kInlineTextBox;
inline_box_6.SetName("3.14");
SetTree(CreateAXTree({root_1, text_field_2, static_text_3, inline_box_4,
static_text_5, inline_box_6}));
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(38, text_position->MaxTextOffset());
EXPECT_EQ(u"Placeholder from generated content3.14",
text_position->GetText());
}
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(
GetTreeID(), root_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->AtStartOfAnchor());
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtStartOfAnchor());
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
3 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtStartOfAnchor());
// A "before text" position.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box1_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->AtStartOfAnchor());
// An "after text" position.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), root_.id, 3 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->AtEndOfAnchor());
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
2 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtEndOfAnchor());
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
EXPECT_FALSE(tree_position->AtEndOfAnchor());
}
TEST_F(AXPositionTest, AtEndOfAnchorWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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, AtStartOfLineStaticTextExtraPrecedingSpace) {
// Consider the following web content:
// <style>
// .required-label::after {
// content: " *";
// }
// </style>
// <label class="required-label">Required </label>
//
// Which has the following AXTree, where the static text (#3)
// contains an extra preceding space compared to its inline text (#4).
// ++1 kRootWebArea
// ++++2 kLabelText
// ++++++3 kStaticText name=" *"
// ++++++++4 kInlineTextBox name="*"
// This test ensures that this difference between static text and its inline
// text box does not cause a hang when AtStartOfLine is called on static text
// with text position " <*>".
AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
// "kIsLineBreakingObject" is not strictly necessary but is added for
// completeness.
root.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData label_text;
label_text.id = 2;
label_text.role = ax::mojom::Role::kLabelText;
AXNodeData static_text1;
static_text1.id = 3;
static_text1.role = ax::mojom::Role::kStaticText;
static_text1.SetName(" *");
AXNodeData inline_text1;
inline_text1.id = 4;
inline_text1.role = ax::mojom::Role::kInlineTextBox;
inline_text1.SetName("*");
static_text1.child_ids = {inline_text1.id};
root.child_ids = {static_text1.id};
SetTree(CreateAXTree({root, static_text1, inline_text1}));
// Calling AtStartOfLine on |static_text1| with position " <*>",
// text_offset_=1, should not get into an infinite loop; it should be
// guaranteed to terminate.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text1.id, 1 /* child_index */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(text_position->AtStartOfLine());
}
TEST_F(AXPositionTest, AtEndOfLineWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(GetTree()->Unserialize(update));
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(GetTree()->Unserialize(update));
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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, AtStartAndEndOfLineWhenAtEndOfTextSpan) {
// This test ensures that the "AtStartOfLine" and the "AtEndOfLine" methods
// return false and true respectively when we are at the end of a text span.
//
// A text span is defined by a series of inline text boxes that make up a
// single static text object. Lines always end at the end of static text
// objects, so there would never arise a situation when a position at the end
// of a text span would be at start of line. It should always be at end of
// line. On the contrary, if a position is at the end of an inline text box
// and the equivalent parent position is in the middle of a static text
// object, then the position would sometimes be at start of line, i.e., when
// the inline text box contains only white space that is used to separate
// lines in the case of lines being wrapped by a soft line break.
//
// Example accessibility tree:
// 0:kRootWebArea
// ++1:kStaticText "Hello testing "
// ++++2:kInlineTextBox "Hello" kNextOnLine=2
// ++++3:kInlineTextBox " " kPreviousOnLine=2
// ++++4:kInlineTextBox "testing" kNextOnLine=5
// ++++5:kInlineTextBox " " kPreviousOnLine=4
// ++6:kStaticText "here."
// ++++7:kInlineTextBox "here."
//
// Resulting text representation:
// "Hello<soft_line_break>testing <hard_line_break>here."
// Notice the extra space after the word "testing". This is not a line break.
// The hard line break is caused by the presence of the second static text
// object.
//
// A position at the end of inline text box 3 should be at start of line,
// whilst a position at the end of inline text box 5 should not.
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
// "kIsLineBreakingObject" is not strictly necessary but is added for
// completeness.
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("Hello testing ");
AXNodeData inline_box_data_1;
inline_box_data_1.id = 3;
inline_box_data_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_1.SetName("hello");
AXNodeData inline_box_data_2;
inline_box_data_2.id = 4;
inline_box_data_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_1.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box_data_2.id);
inline_box_data_2.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box_data_1.id);
// The name is a space character that we assume it turns into a soft line
// break by the layout engine.
inline_box_data_2.SetName(" ");
AXNodeData inline_box_data_3;
inline_box_data_3.id = 5;
inline_box_data_3.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_3.SetName("testing");
AXNodeData inline_box_data_4;
inline_box_data_4.id = 6;
inline_box_data_4.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_3.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box_data_4.id);
inline_box_data_4.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box_data_3.id);
inline_box_data_4.SetName(" "); // Just a space character - not a line break.
AXNodeData static_text_data_2;
static_text_data_2.id = 7;
static_text_data_2.role = ax::mojom::Role::kStaticText;
static_text_data_2.SetName("here.");
AXNodeData inline_box_data_5;
inline_box_data_5.id = 8;
inline_box_data_5.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_5.SetName("here.");
static_text_data_1.child_ids = {inline_box_data_1.id, inline_box_data_2.id,
inline_box_data_3.id, inline_box_data_4.id};
static_text_data_2.child_ids = {inline_box_data_5.id};
root_data.child_ids = {static_text_data_1.id, static_text_data_2.id};
SetTree(CreateAXTree({root_data, static_text_data_1, inline_box_data_1,
inline_box_data_2, inline_box_data_3, inline_box_data_4,
static_text_data_2, inline_box_data_5}));
// An "after text" tree position - after the soft line break.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_data_2.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_TRUE(tree_position->AtStartOfLine());
EXPECT_FALSE(tree_position->AtEndOfLine());
// An "after text" tree position - after the space character and before the
// hard line break caused by the second static text object.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_data_4.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_FALSE(tree_position->AtStartOfLine());
EXPECT_TRUE(tree_position->AtEndOfLine());
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_data_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfLine());
EXPECT_FALSE(text_position->AtEndOfLine());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_data_4.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfLine());
EXPECT_TRUE(text_position->AtEndOfLine());
}
TEST_F(AXPositionTest, AtStartAndEndOfLineInsideTextField) {
// This test ensures that "AtStart/EndOfLine" methods work properly when at
// the start or end of a text field.
//
// We setup a test tree with two text fields. The first one has one line of
// text, and the second one three. There are inline text boxes containing only
// white space at the start and end of both text fields, which is a valid
// AXTree that might be generated by our renderer.
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
// "kIsLineBreakingObject" is not strictly necessary but is added for
// completeness.
root_data.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
AXNodeData text_field_data_1;
text_field_data_1.id = 2;
text_field_data_1.role = ax::mojom::Role::kTextField;
text_field_data_1.AddState(ax::mojom::State::kEditable);
// "kIsLineBreakingObject" is not strictly necessary.
text_field_data_1.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
text_field_data_1.AddState(ax::mojom::State::kEditable);
// Notice that there is one space at the start and one at the end of the text
// field's value.
text_field_data_1.SetValue(" Text field one ");
AXNodeData static_text_data_1;
static_text_data_1.id = 3;
static_text_data_1.role = ax::mojom::Role::kStaticText;
static_text_data_1.AddState(ax::mojom::State::kEditable);
static_text_data_1.SetName(" Text field one ");
AXNodeData inline_box_data_1;
inline_box_data_1.id = 4;
inline_box_data_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_1.AddState(ax::mojom::State::kEditable);
inline_box_data_1.SetName(" ");
AXNodeData inline_box_data_2;
inline_box_data_2.id = 5;
inline_box_data_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_2.AddState(ax::mojom::State::kEditable);
inline_box_data_1.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box_data_2.id);
inline_box_data_2.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box_data_1.id);
inline_box_data_2.SetName("Text field one");
AXNodeData inline_box_data_3;
inline_box_data_3.id = 6;
inline_box_data_3.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_3.AddState(ax::mojom::State::kEditable);
inline_box_data_2.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box_data_3.id);
inline_box_data_3.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box_data_2.id);
inline_box_data_3.SetName(" ");
AXNodeData text_field_data_2;
text_field_data_2.id = 7;
text_field_data_2.role = ax::mojom::Role::kTextField;
text_field_data_2.AddState(ax::mojom::State::kEditable);
// "kIsLineBreakingObject" is not strictly necessary.
text_field_data_2.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
// Notice that there are three lines, the first and the last one include only
// a single space.
text_field_data_2.SetValue(" Text field two ");
AXNodeData static_text_data_2;
static_text_data_2.id = 8;
static_text_data_2.role = ax::mojom::Role::kStaticText;
static_text_data_2.AddState(ax::mojom::State::kEditable);
static_text_data_2.SetName(" Text field two ");
AXNodeData inline_box_data_4;
inline_box_data_4.id = 9;
inline_box_data_4.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_4.AddState(ax::mojom::State::kEditable);
inline_box_data_4.SetName(" ");
AXNodeData inline_box_data_5;
inline_box_data_5.id = 10;
inline_box_data_5.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_5.AddState(ax::mojom::State::kEditable);
inline_box_data_5.SetName("Text field two");
AXNodeData inline_box_data_6;
inline_box_data_6.id = 11;
inline_box_data_6.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_6.AddState(ax::mojom::State::kEditable);
inline_box_data_6.SetName(" ");
static_text_data_1.child_ids = {inline_box_data_1.id, inline_box_data_2.id,
inline_box_data_3.id};
static_text_data_2.child_ids = {inline_box_data_4.id, inline_box_data_5.id,
inline_box_data_6.id};
text_field_data_1.child_ids = {static_text_data_1.id};
text_field_data_2.child_ids = {static_text_data_2.id};
root_data.child_ids = {text_field_data_1.id, text_field_data_2.id};
SetTree(
CreateAXTree({root_data, text_field_data_1, static_text_data_1,
inline_box_data_1, inline_box_data_2, inline_box_data_3,
text_field_data_2, static_text_data_2, inline_box_data_4,
inline_box_data_5, inline_box_data_6}));
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_data_1.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_TRUE(tree_position->AtStartOfLine());
EXPECT_FALSE(tree_position->AtEndOfLine());
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_data_1.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_FALSE(tree_position->AtStartOfLine());
EXPECT_TRUE(tree_position->AtEndOfLine());
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_data_2.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_TRUE(tree_position->AtStartOfLine());
EXPECT_FALSE(tree_position->AtEndOfLine());
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_data_2.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_FALSE(tree_position->AtStartOfLine());
EXPECT_TRUE(tree_position->AtEndOfLine());
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_data_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfLine());
EXPECT_FALSE(text_position->AtEndOfLine());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_data_1.id, 16 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfLine());
EXPECT_TRUE(text_position->AtEndOfLine());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_data_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->AtStartOfLine());
EXPECT_FALSE(text_position->AtEndOfLine());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_data_2.id, 16 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
EXPECT_FALSE(text_position->AtStartOfLine());
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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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 because in practice
// they should have resulted from two different ancestor positions. The former
// should have been an upstream position, whilst the latter a downstream one.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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 the whole content.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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 ensures that "At{Start|End}OfParagraph" work correctly when a
// text position is on a preserved newline character.
//
// Newline characters are used to separate paragraphs. If there is a series of
// newline characters, a paragraph should start after the last newline
// character.
// ++1 kRootWebArea isLineBreakingObject
// ++++2 kStaticText "some text"
// ++++++3 kInlineTextBox "some text"
// ++++4 kGenericContainer isLineBreakingObject
// ++++++5 kStaticText "\nmore text"
// ++++++++6 kInlineTextBox "\n" isLineBreakingObject
// ++++++++7 kInlineTextBox "more text"
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 = {some_text_data.id};
container_data.child_ids = {static_text_data_2.id};
static_text_data_2.child_ids = {preserved_newline_data.id, more_text_data.id};
root_data.child_ids = {static_text_data_1.id, container_data.id};
SetTree(CreateAXTree({root_data, static_text_data_1, some_text_data,
container_data, static_text_data_2,
preserved_newline_data, more_text_data}));
// Text position "some tex<t>\nmore text".
TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 8 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position1->AtEndOfParagraph());
EXPECT_FALSE(text_position1->AtStartOfParagraph());
// Text position "some text<\n>more text".
TestPositionType text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position2->AtEndOfParagraph());
EXPECT_FALSE(text_position2->AtStartOfParagraph());
// Text position "some text<\n>more text".
TestPositionType text_position3 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
EXPECT_FALSE(text_position3->AtEndOfParagraph());
EXPECT_FALSE(text_position3->AtStartOfParagraph());
// Text position "some text\n<m>ore text".
TestPositionType text_position4 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position4->AtEndOfParagraph());
EXPECT_TRUE(text_position4->AtStartOfParagraph());
// Text position "some text\n<m>ore text".
TestPositionType text_position5 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
EXPECT_TRUE(text_position5->AtEndOfParagraph());
EXPECT_FALSE(text_position5->AtStartOfParagraph());
// Text position "<\n>more text".
TestPositionType text_position6 = AXNodePosition::CreateTextPosition(
GetTreeID(), container_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position6->AtEndOfParagraph());
EXPECT_FALSE(text_position6->AtStartOfParagraph());
// Text position "\n<m>ore text".
TestPositionType text_position7 = AXNodePosition::CreateTextPosition(
GetTreeID(), container_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position7->AtEndOfParagraph());
EXPECT_TRUE(text_position7->AtStartOfParagraph());
// Text position "\n<m>ore text".
TestPositionType text_position8 = AXNodePosition::CreateTextPosition(
GetTreeID(), container_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
EXPECT_TRUE(text_position8->AtEndOfParagraph());
EXPECT_FALSE(text_position8->AtStartOfParagraph());
// Text position "\n<m>ore text".
TestPositionType text_position9 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_data_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position9->AtEndOfParagraph());
EXPECT_TRUE(text_position9->AtStartOfParagraph());
// Text position "\n<m>ore text".
TestPositionType text_position10 = AXNodePosition::CreateTextPosition(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), more_text_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position14->AtEndOfParagraph());
EXPECT_FALSE(text_position14->AtStartOfParagraph());
}
TEST_F(
AXPositionTest,
PreviousParagraphEndStopAtAnchorBoundaryWithConsecutiveParentChildLineBreakingObjects) {
// This test updates the tree structure to test a specific edge case -
// CreatePreviousParagraphEndPosition(), stopping at an anchor boundary,
// with consecutive parent-child line breaking objects.
// ++1 rootWebArea
// ++++2 staticText name="first"
// ++++3 genericContainer isLineBreakingObject
// ++++++4 genericContainer isLineBreakingObject
// ++++++5 staticText name="second"
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
AXNodeData static_text_data_a;
static_text_data_a.id = 2;
static_text_data_a.role = ax::mojom::Role::kStaticText;
static_text_data_a.SetName("first");
AXNodeData container_data_a;
container_data_a.id = 3;
container_data_a.role = ax::mojom::Role::kGenericContainer;
container_data_a.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
AXNodeData container_data_b;
container_data_b.id = 4;
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 = 5;
static_text_data_b.role = ax::mojom::Role::kStaticText;
static_text_data_b.SetName("second");
root_data.child_ids = {static_text_data_a.id, container_data_a.id};
container_data_a.child_ids = {container_data_b.id, static_text_data_b.id};
SetTree(CreateAXTree({root_data, static_text_data_a, container_data_a,
container_data_b, static_text_data_b}));
TestPositionType test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 11 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
test_position = test_position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(root_data.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
}
TEST_F(AXPositionTest, AtStartOrEndOfParagraphOnAListMarker) {
// "AtStartOfParagraph" should return true before a list marker, either a
// Legacy Layout or an NG Layout one. It should return false on the next
// sibling of the list marker, i.e., before the list item's actual text
// contents.
//
// There are two list markers in the following test tree. The first one is a
// Legacy Layout one and the second an NG Layout one.
// ++1 kRootWebArea
// ++++2 kStaticText "Before list."
// ++++++3 kInlineTextBox "Before list."
// ++++4 kList
// ++++++5 kListItem
// ++++++++6 kListMarker
// ++++++++++7 kStaticText "1. "
// ++++++++++++8 kInlineTextBox "1. "
// ++++++++9 kStaticText "First item."
// ++++++++++10 kInlineTextBox "First item."
// ++++++11 kListItem
// ++++++++12 kListMarker "2. "
// ++++++++13 kStaticText "Second item."
// ++++++++++14 kInlineTextBox "Second item."
// ++15 kStaticText "After list."
// ++++16 kInlineTextBox "After list."
AXNodeData root;
AXNodeData list;
AXNodeData list_item1;
AXNodeData list_item2;
AXNodeData list_marker_legacy;
AXNodeData list_marker_ng;
AXNodeData static_text1;
AXNodeData static_text2;
AXNodeData static_text3;
AXNodeData static_text4;
AXNodeData static_text5;
AXNodeData inline_box1;
AXNodeData inline_box2;
AXNodeData inline_box3;
AXNodeData inline_box4;
AXNodeData inline_box5;
root.id = 1;
static_text1.id = 2;
inline_box1.id = 3;
list.id = 4;
list_item1.id = 5;
list_marker_legacy.id = 6;
static_text2.id = 7;
inline_box2.id = 8;
static_text3.id = 9;
inline_box3.id = 10;
list_item2.id = 11;
list_marker_ng.id = 12;
static_text4.id = 13;
inline_box4.id = 14;
static_text5.id = 15;
inline_box5.id = 16;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids = {static_text1.id, list.id, static_text5.id};
root.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
static_text1.role = ax::mojom::Role::kStaticText;
static_text1.child_ids = {inline_box1.id};
static_text1.SetName("Before list.");
inline_box1.role = ax::mojom::Role::kInlineTextBox;
inline_box1.SetName("Before list.");
list.role = ax::mojom::Role::kList;
list.child_ids = {list_item1.id, list_item2.id};
list_item1.role = ax::mojom::Role::kListItem;
list_item1.child_ids = {list_marker_legacy.id, static_text3.id};
list_item1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
list_marker_legacy.role = ax::mojom::Role::kListMarker;
list_marker_legacy.child_ids = {static_text2.id};
static_text2.role = ax::mojom::Role::kStaticText;
static_text2.child_ids = {inline_box2.id};
static_text2.SetName("1. ");
inline_box2.role = ax::mojom::Role::kInlineTextBox;
inline_box2.SetName("1. ");
inline_box2.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box3.id);
static_text3.role = ax::mojom::Role::kStaticText;
static_text3.child_ids = {inline_box3.id};
static_text3.SetName("First item.");
inline_box3.role = ax::mojom::Role::kInlineTextBox;
inline_box3.SetName("First item.");
inline_box3.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box2.id);
list_item2.role = ax::mojom::Role::kListItem;
list_item2.child_ids = {list_marker_ng.id, static_text4.id};
list_item2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
list_marker_ng.role = ax::mojom::Role::kListMarker;
list_marker_ng.SetName("2. ");
list_marker_ng.SetNameFrom(ax::mojom::NameFrom::kContents);
list_marker_ng.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box4.id);
static_text4.role = ax::mojom::Role::kStaticText;
static_text4.child_ids = {inline_box4.id};
static_text4.SetName("Second item.");
inline_box4.role = ax::mojom::Role::kInlineTextBox;
inline_box4.SetName("Second item.");
inline_box4.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
list_marker_ng.id);
static_text5.role = ax::mojom::Role::kStaticText;
static_text5.child_ids = {inline_box5.id};
static_text5.SetName("After list.");
inline_box5.role = ax::mojom::Role::kInlineTextBox;
inline_box5.SetName("After list.");
SetTree(CreateAXTree({root, static_text1, inline_box1, list, list_item1,
list_marker_legacy, static_text2, inline_box2,
static_text3, inline_box3, list_item2, list_marker_ng,
static_text4, inline_box4, static_text5, inline_box5}));
// A text position after the text "Before list.". It should not be equivalent
// to a position that is before the list itself, or before the first list
// bullet / item.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text1.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_TRUE(text_position->AtEndOfParagraph());
// A text position after the text "Before list.". It should not be equivalent
// to a position that is before the list itself, or before the first list
// bullet / item.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_TRUE(text_position->AtEndOfParagraph());
// A text position before the list.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), list.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A downstream text position after the list. It should resolve to a leaf
// position before the paragraph that comes after the list, so it should be
// "AtStartOfParagraph".
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), list.id, 14 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// An upstream text position after the list. It should be "AtEndOfParagraph".
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), list.id, 14 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_TRUE(text_position->AtEndOfParagraph());
// A text position before the first list bullet (the Legacy Layout one).
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), list_marker_legacy.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), list_marker_legacy.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A text position before the first list bullet (the Legacy Layout one).
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text2.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A text position before the first list bullet (the Legacy Layout one).
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A text position before the second list bullet (the NG Layout one).
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), list_marker_ng.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), list_marker_ng.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A text position before the text contents of the first list item - not the
// bullet.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text3.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A text position before the text contents of the first list item - not the
// bullet.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box3.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A text position after the text contents of the first list item.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text3.id, 11 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_TRUE(text_position->AtEndOfParagraph());
// A text position after the text contents of the first list item.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box3.id, 11 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_TRUE(text_position->AtEndOfParagraph());
// A text position before the text contents of the second list item - not the
// bullet.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text4.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A text position before the text contents of the second list item - not the
// bullet.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box4.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
// A text position after the text contents of the second list item.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text4.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_TRUE(text_position->AtEndOfParagraph());
// A text position after the text contents of the second list item.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box4.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_FALSE(text_position->AtStartOfParagraph());
EXPECT_TRUE(text_position->AtEndOfParagraph());
// A text position before the text "After list.".
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box5.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->AtStartOfParagraph());
EXPECT_FALSE(text_position->AtEndOfParagraph());
}
TEST_F(AXPositionTest,
AtStartOrEndOfParagraphWithLeadingAndTrailingWhitespace) {
// This test ensures that "At{Start|End}OfParagraph" work correctly when a
// text position is on a preserved newline character.
//
// Newline characters are used to separate paragraphs. If there is a series of
// newline characters, a paragraph should start after the last newline
// character.
// ++1 kRootWebArea isLineBreakingObject
// ++++2 kGenericContainer isLineBreakingObject
// ++++++3 kStaticText "\n"
// ++++++++4 kInlineTextBox "\n" isLineBreakingObject
// ++++5 kGenericContainer isLineBreakingObject
// ++++++6 kStaticText "some text"
// ++++++++7 kInlineTextBox "some"
// ++++++++8 kInlineTextBox " "
// ++++++++9 kInlineTextBox "text"
// ++++10 kGenericContainer isLineBreakingObject
// ++++++11 kStaticText "\n"
// ++++++++12 kInlineTextBox "\n" isLineBreakingObject
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};
SetTree(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}));
// Before the first "\n".
TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_a.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position1->AtEndOfParagraph());
EXPECT_TRUE(text_position1->AtStartOfParagraph());
// After the first "\n".
//
// Since the position is an "after text" position, it is similar to pressing
// the End key, (or Cmd-Right on Mac), while the caret is on the line break,
// so it should not be "AtStartOfParagraph".
TestPositionType text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_a.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position2->AtEndOfParagraph());
EXPECT_FALSE(text_position2->AtStartOfParagraph());
// Before "some".
TestPositionType text_position3 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position3->AtEndOfParagraph());
EXPECT_TRUE(text_position3->AtStartOfParagraph());
// After "some".
TestPositionType text_position4 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_1.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position4->AtEndOfParagraph());
EXPECT_FALSE(text_position4->AtStartOfParagraph());
// Before " ".
TestPositionType text_position5 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position5->AtEndOfParagraph());
EXPECT_FALSE(text_position5->AtStartOfParagraph());
// After " ".
TestPositionType text_position6 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position6->AtEndOfParagraph());
EXPECT_FALSE(text_position6->AtStartOfParagraph());
// Before "text".
TestPositionType text_position7 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_3.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position7->AtEndOfParagraph());
EXPECT_FALSE(text_position7->AtStartOfParagraph());
// After "text".
TestPositionType text_position8 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_3.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position8->AtEndOfParagraph());
EXPECT_FALSE(text_position8->AtStartOfParagraph());
// Before the second "\n".
TestPositionType text_position9 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_c.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position9->AtEndOfParagraph());
EXPECT_FALSE(text_position9->AtStartOfParagraph());
// After the second "\n".
TestPositionType text_position10 = AXNodePosition::CreateTextPosition(
GetTreeID(), 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, AtStartOrEndOfParagraphWithIgnoredNodes) {
// This test ensures that "At{Start|End}OfParagraph" work correctly when there
// are ignored nodes present near a paragraph boundary.
//
// An ignored node that is between a given position and a paragraph boundary
// should not be taken into consideration. The position should be interpreted
// as being on the boundary.
// ++1 kRootWebArea isLineBreakingObject
// ++++2 kGenericContainer ignored isLineBreakingObject
// ++++++3 kStaticText ignored "ignored text"
// ++++++++4 kInlineTextBox ignored "ignored text"
// ++++5 kGenericContainer isLineBreakingObject
// ++++++6 kStaticText "some text"
// ++++++++7 kInlineTextBox "some"
// ++++++++8 kInlineTextBox " "
// ++++++++9 kInlineTextBox "text"
// ++++10 kGenericContainer ignored isLineBreakingObject
// ++++++11 kStaticText ignored "ignored text"
// ++++++++12 kInlineTextBox ignored "ignored text"
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.AddState(ax::mojom::State::kIgnored);
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("ignored text");
static_text_data_a.AddState(ax::mojom::State::kIgnored);
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("ignored text");
inline_text_data_a.AddState(ax::mojom::State::kIgnored);
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.AddState(ax::mojom::State::kIgnored);
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("ignored text");
static_text_data_c.AddState(ax::mojom::State::kIgnored);
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("ignored text");
inline_text_data_c.AddState(ax::mojom::State::kIgnored);
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};
SetTree(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}));
// Before "ignored text".
TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_a.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position1->AtEndOfParagraph());
EXPECT_FALSE(text_position1->AtStartOfParagraph());
// After "ignored text".
//
// Since the position is an "after text" position, it is similar to pressing
// the End key, (or Cmd-Right on Mac), while the caret is on "ignored text",
// so it should not be "AtStartOfParagraph". In practice, this situation
// should not arise in accessibility, because the node is ignored.
TestPositionType text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_a.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position2->AtEndOfParagraph());
EXPECT_FALSE(text_position2->AtStartOfParagraph());
// Before "some".
TestPositionType text_position3 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position3->AtEndOfParagraph());
EXPECT_TRUE(text_position3->AtStartOfParagraph());
// After "some".
TestPositionType text_position4 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_1.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position4->AtEndOfParagraph());
EXPECT_FALSE(text_position4->AtStartOfParagraph());
// Before " ".
TestPositionType text_position5 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position5->AtEndOfParagraph());
EXPECT_FALSE(text_position5->AtStartOfParagraph());
// After " ".
TestPositionType text_position6 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position6->AtEndOfParagraph());
EXPECT_FALSE(text_position6->AtStartOfParagraph());
// Before "text".
TestPositionType text_position7 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_3.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position7->AtEndOfParagraph());
EXPECT_FALSE(text_position7->AtStartOfParagraph());
// After "text".
TestPositionType text_position8 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_b_3.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position8->AtEndOfParagraph());
EXPECT_FALSE(text_position8->AtStartOfParagraph());
// Before "ignored text" - the second version.
TestPositionType text_position9 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_c.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position9->AtEndOfParagraph());
EXPECT_FALSE(text_position9->AtStartOfParagraph());
// After "ignored text" - the second version.
TestPositionType text_position10 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_text_data_c.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position10->AtEndOfParagraph());
EXPECT_FALSE(text_position10->AtStartOfParagraph());
}
TEST_F(AXPositionTest, AtStartOrEndOfParagraphWithEmbeddedObjectCharacter) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// This test ensures that "At{Start|End}OfParagraph" work correctly when there
// are embedded objects present near a paragraph boundary.
//
// Nodes represented by an embedded object character, such as an <input> or a
// <textarea> text field, or a check box, should create an implicit paragraph
// boundary for assistive software.
// ++1 kRootWebArea isLineBreakingObject
// ++++2 kLink
// ++++++3 kStaticText "hello"
// ++++++++4 kInlineTextBox "hello"
// ++++++5 kImage
// ++++++6 kStaticText "world"
// ++++++++7 kInlineTextBox "world"
AXNodeData root_1;
AXNodeData link_2;
AXNodeData static_text_3;
AXNodeData inline_box_4;
AXNodeData image_5;
AXNodeData static_text_6;
AXNodeData inline_box_7;
root_1.id = 1;
link_2.id = 2;
static_text_3.id = 3;
inline_box_4.id = 4;
image_5.id = 5;
static_text_6.id = 6;
inline_box_7.id = 7;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {link_2.id};
root_1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
link_2.role = ax::mojom::Role::kLink;
link_2.child_ids = {static_text_3.id, image_5.id, static_text_6.id};
static_text_3.role = ax::mojom::Role::kStaticText;
static_text_3.child_ids = {inline_box_4.id};
static_text_3.SetName("Hello");
inline_box_4.role = ax::mojom::Role::kInlineTextBox;
inline_box_4.SetName("Hello");
image_5.role = ax::mojom::Role::kImage;
// The image's inner text should be an embedded object character.
static_text_6.role = ax::mojom::Role::kStaticText;
static_text_6.child_ids = {inline_box_7.id};
static_text_6.SetName("world");
inline_box_7.role = ax::mojom::Role::kInlineTextBox;
inline_box_7.SetName("world");
SetTree(CreateAXTree({root_1, link_2, static_text_3, inline_box_4, image_5,
static_text_6, inline_box_7}));
// Before "hello".
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_4.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position->AtEndOfParagraph());
EXPECT_TRUE(text_position->AtStartOfParagraph());
// After "hello".
//
// Note that even though this position and a position before the image's
// embedded object character are conceptually equivalent, in practice they
// should result from two different ancestor positions. The former should have
// been an upstream position, whilst the latter a downstream one.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_4.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->AtEndOfParagraph());
EXPECT_FALSE(text_position->AtStartOfParagraph());
// Before the image's embedded object character.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), image_5.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position->AtEndOfParagraph());
EXPECT_TRUE(text_position->AtStartOfParagraph());
// After the image's embedded object character.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), image_5.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->AtEndOfParagraph());
EXPECT_FALSE(text_position->AtStartOfParagraph());
// Before "world".
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_7.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(text_position->AtEndOfParagraph());
EXPECT_TRUE(text_position->AtStartOfParagraph());
// After "world".
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_7.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->AtEndOfParagraph());
EXPECT_FALSE(text_position->AtStartOfParagraph());
}
TEST_F(AXPositionTest, LowestCommonAncestor) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
// An "after children" position.
TestPositionType root_position = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 3 /* child_index */);
ASSERT_NE(nullptr, root_position);
// A "before text" position.
TestPositionType button_position = AXNodePosition::CreateTreePosition(
GetTreeID(), button_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, button_position);
TestPositionType text_field_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 2 /* child_index */);
ASSERT_NE(nullptr, text_field_position);
TestPositionType static_text1_position = AXNodePosition::CreateTreePosition(
GetTreeID(), static_text1_.id, 0 /* child_index */);
ASSERT_NE(nullptr, static_text1_position);
TestPositionType static_text2_position = AXNodePosition::CreateTreePosition(
GetTreeID(), static_text2_.id, 0 /* child_index */);
ASSERT_NE(nullptr, static_text2_position);
TestPositionType inline_box1_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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(
GetTreeID(), 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(), ax::mojom::MoveDirection::kForward);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = root_position->LowestCommonAncestor(
*root_position.get(), ax::mojom::MoveDirection::kForward);
ASSERT_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(), ax::mojom::MoveDirection::kForward);
ASSERT_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(), ax::mojom::MoveDirection::kForward);
ASSERT_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(), ax::mojom::MoveDirection::kForward);
ASSERT_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(), ax::mojom::MoveDirection::kForward);
ASSERT_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(), ax::mojom::MoveDirection::kForward);
ASSERT_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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(GetTreeID(), 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, AsLeafTreePositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, AsLeafTreePositionWithTreePosition) {
// Create a tree position pointing to the first static text node inside the
// text field: a "before children" position.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// Create a tree position pointing to the line break node inside the text
// field.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// Create a text position pointing to the second static text node inside the
// text field.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 2 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
}
TEST_F(AXPositionTest, AsLeafTreePositionWithTextPosition) {
// Create a text position pointing to the end of the root (an "after text"
// position).
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// Nodes with no text should not be skipped when finding the leaf text
// position, otherwise a "before text" position could accidentally turn into
// an "after text" one.
// ++kTextField "" (empty)
// ++++kStaticText "" (empty)
// ++++++kInlineTextBox "" (empty)
// A TextPosition anchor=kTextField text_offset=0, should turn into a leaf
// text position at the start of kInlineTextBox and not after it. In this
// case, the deepest first child of the root is the button, regardless as to
// whether it has no text inside it.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// 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(
GetTreeID(), root_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// 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(
GetTreeID(), text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// Create a text position on the root, pointing to the line break character
// inside the text field.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// Create a text position pointing to the line break character inside the text
// field.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// Create a text position pointing to the offset after the last character in
// the text field, (an "after text" position).
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// Create a root text position that points to the middle of an equivalent leaf
// text position.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->AsLeafTreePosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTreePosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, 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(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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(
GetTreeID(), root_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_FALSE(text_position->IsLeafTextPosition());
TestPositionType test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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(
GetTreeID(), root_.id, 0 /* 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(GetTreeID(), test_position->tree_id());
EXPECT_EQ(button_.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(
GetTreeID(), text_field_.id, 0 /* 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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(
GetTreeID(), text_field_.id, 0 /* 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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 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(
GetTreeID(), root_.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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
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());
// 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(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
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());
// Create a text position on the root, pointing to the line break character
// inside the text field.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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 an equivalent leaf
// text position. It should maintain its relative text_offset ("Lin<e> 2")
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), 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());
}
TEST_F(AXPositionTest, AsLeafTextPositionWithTextPositionAndEmptyTextSandwich) {
// This test updates the tree structure to test a specific edge case -
// `AsLeafTextPosition` when there is an empty leaf text node between
// two non-empty text nodes. Empty leaf nodes should not be skipped when
// finding the leaf equivalent position, otherwise important controls (e.g.
// buttons) that are unlabelled could accidentally be skipped while
// navigating.
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::kInlineTextBox;
text_data.SetName("some text");
AXNodeData button_data;
button_data.id = 3;
button_data.role = ax::mojom::Role::kButton;
button_data.SetName("");
button_data.SetNameFrom(ax::mojom::NameFrom::kContents);
AXNodeData more_text_data;
more_text_data.id = 4;
more_text_data.role = ax::mojom::Role::kInlineTextBox;
more_text_data.SetName("more text");
root_data.child_ids = {text_data.id, button_data.id, more_text_data.id};
SetTree(CreateAXTree({root_data, text_data, button_data, more_text_data}));
// Create a text position on the root pointing to just after the
// first static text leaf node. Even though the button has empty inner text,
// still, it should not be skipped when finding the leaf text position.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_FALSE(text_position->IsLeafTextPosition());
TestPositionType test_position = text_position->AsLeafTextPosition();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(button_data.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(
GetTreeID(), root_data.id, 9 /* 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->IsLeafTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(text_data.id, test_position->anchor_id());
EXPECT_EQ(9, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, AsLeafTextPositionWithTextPositionAndEmbeddedObject) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea "<embedded_object><embedded_object>"
// ++++2 kImage alt="Test image"
// ++++3 kParagraph "<embedded_object>"
// ++++++4 kLink "Hello"
// ++++++++5 kStaticText "Hello"
// ++++++++++6 kInlineTextBox "Hello"
AXNodeData root;
AXNodeData image;
AXNodeData paragraph;
AXNodeData link;
AXNodeData static_text;
AXNodeData inline_box;
root.id = 1;
image.id = 2;
paragraph.id = 3;
link.id = 4;
static_text.id = 5;
inline_box.id = 6;
root.role = ax::mojom::Role::kRootWebArea;
root.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
root.child_ids = {image.id, paragraph.id};
image.role = ax::mojom::Role::kImage;
image.SetName("Test image");
// Alt text should not appear in the tree's text representation, so we need to
// set the right NameFrom.
image.SetNameFrom(ax::mojom::NameFrom::kAttribute);
paragraph.role = ax::mojom::Role::kParagraph;
paragraph.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph.child_ids = {link.id};
link.role = ax::mojom::Role::kLink;
link.AddState(ax::mojom::State::kLinked);
link.child_ids = {static_text.id};
static_text.role = ax::mojom::Role::kStaticText;
static_text.SetName("Hello");
static_text.child_ids = {inline_box.id};
inline_box.role = ax::mojom::Role::kInlineTextBox;
inline_box.SetName("Hello");
SetTree(
CreateAXTree({root, image, paragraph, link, static_text, inline_box}));
TestPositionType before_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_root);
TestPositionType middle_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, middle_root);
TestPositionType middle_root_upstream = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, middle_root_upstream);
TestPositionType after_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_root);
// A position with an upstream affinity after the root should make no
// difference compared with a downstream affinity, but we'll test it for
// completeness.
TestPositionType after_root_upstream = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, after_root_upstream);
TestPositionType before_image = AXNodePosition::CreateTextPosition(
GetTreeID(), image.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_image);
// Alt text should not appear in the tree's text representation, but since the
// image is both a character and a word boundary it should be replaced by the
// "embedded object replacement character" in the text representation.
TestPositionType after_image = AXNodePosition::CreateTextPosition(
GetTreeID(), image.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_image);
TestPositionType before_paragraph = AXNodePosition::CreateTextPosition(
GetTreeID(), paragraph.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_paragraph);
// The paragraph has a link inside it, so it will only expose a single
// "embedded object replacement character".
TestPositionType after_paragraph = AXNodePosition::CreateTextPosition(
GetTreeID(), paragraph.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_paragraph);
// A position with an upstream affinity after the paragraph should make no
// difference compared with a downstream affinity, but we'll test it for
// completeness.
TestPositionType after_paragraph_upstream =
AXNodePosition::CreateTextPosition(GetTreeID(), paragraph.id,
1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, after_paragraph_upstream);
TestPositionType before_link = AXNodePosition::CreateTextPosition(
GetTreeID(), link.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_link);
// The llink has the text "Hello" inside it.
TestPositionType after_link = AXNodePosition::CreateTextPosition(
GetTreeID(), link.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_link);
// A position with an upstream affinity after the link should make no
// difference compared with a downstream affinity, but we'll test it for
// completeness.
TestPositionType after_link_upstream = AXNodePosition::CreateTextPosition(
GetTreeID(), link.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, after_link_upstream);
TestPositionType before_inline_box = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_inline_box);
// The inline box has the text "Hello" inside it.
TestPositionType after_inline_box = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_inline_box);
EXPECT_EQ(*before_root->AsLeafTextPosition(), *before_image);
EXPECT_EQ(*middle_root->AsLeafTextPosition(), *before_inline_box);
// As mentioned above, alt text should not appear in the tree's text
// representation, but since the image is both a character and a word boundary
// it should be replaced by the "embedded object replacement character" in the
// text representation.
EXPECT_EQ(*middle_root_upstream->AsLeafTextPosition(), *after_image);
EXPECT_EQ(*after_root->AsLeafTextPosition(), *after_inline_box);
EXPECT_EQ(*after_root_upstream->AsLeafTextPosition(), *after_inline_box);
EXPECT_EQ(*before_paragraph->AsLeafTextPosition(), *before_inline_box);
EXPECT_EQ(*after_paragraph->AsLeafTextPosition(), *after_inline_box);
EXPECT_EQ(*after_paragraph_upstream->AsLeafTextPosition(), *after_inline_box);
EXPECT_EQ(*before_link->AsLeafTextPosition(), *before_inline_box);
EXPECT_EQ(*after_link->AsLeafTextPosition(), *after_inline_box);
EXPECT_EQ(*after_link_upstream->AsLeafTextPosition(), *after_inline_box);
}
TEST_F(AXPositionTest, AsUnignoredPosition) {
// ++root_data
// ++++static_text_data_1 "1"
// ++++++inline_box_data_1 "1"
// ++++++inline_box_data_1 "2" ignored
// ++++container_data ignored
// ++++++static_data_2 "3"
// ++++++++inline_box_data_2 "3"
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
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("1");
AXNodeData inline_box_data_1;
inline_box_data_1.id = 3;
inline_box_data_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_1.SetName("1");
AXNodeData inline_box_data_2;
inline_box_data_2.id = 4;
inline_box_data_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_2.SetName("2");
inline_box_data_2.AddState(ax::mojom::State::kIgnored);
AXNodeData container_data;
container_data.id = 5;
container_data.role = ax::mojom::Role::kGenericContainer;
container_data.AddState(ax::mojom::State::kIgnored);
AXNodeData static_text_data_2;
static_text_data_2.id = 6;
static_text_data_2.role = ax::mojom::Role::kStaticText;
static_text_data_2.SetName("3");
AXNodeData inline_box_data_3;
inline_box_data_3.id = 7;
inline_box_data_3.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_3.SetName("3");
static_text_data_1.child_ids = {inline_box_data_1.id, inline_box_data_2.id};
container_data.child_ids = {static_text_data_2.id};
static_text_data_2.child_ids = {inline_box_data_3.id};
root_data.child_ids = {static_text_data_1.id, container_data.id};
SetTree(CreateAXTree({root_data, static_text_data_1, inline_box_data_1,
inline_box_data_2, container_data, static_text_data_2,
inline_box_data_3}));
// 1. In the case of a text position, we move up the parent positions until we
// find the next unignored equivalent parent position. We don't do this for
// tree positions because, unlike text positions which maintain the
// corresponding text offset in the inner text of the parent node, tree
// positions would lose some information every time a parent position is
// computed. In other words, the parent position of a tree position is, in
// most cases, non-equivalent to the child position.
// "Before text" position.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), container_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->IsIgnored());
TestPositionType test_position = text_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(root_data.id, test_position->anchor_id());
EXPECT_EQ(1, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// "After text" position.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), container_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->IsIgnored());
// Changing the adjustment behavior should not affect the outcome.
test_position = text_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(root_data.id, test_position->anchor_id());
EXPECT_EQ(2, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
// "Before children" position.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), container_data.id, 0 /* child_index */);
EXPECT_TRUE(tree_position->IsIgnored());
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// "After children" position.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), container_data.id, 1 /* child_index */);
EXPECT_TRUE(tree_position->IsIgnored());
// Changing the adjustment behavior should not affect the outcome.
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// "After children" tree positions that are anchored to an unignored node
// whose last child is ignored.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), static_text_data_1.id, 2 /* child_index */);
EXPECT_TRUE(tree_position->IsIgnored());
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_1.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// 2. If no equivalent and unignored parent position can be computed, we try
// computing the leaf equivalent position. If this is unignored, we return it.
// This can happen both for tree and text positions, provided that the leaf
// node and its inner text is visible to platform APIs, i.e. it's unignored.
root_data.AddState(ax::mojom::State::kIgnored);
SetTree(CreateAXTree({root_data, static_text_data_1, inline_box_data_1,
inline_box_data_2, container_data, static_text_data_2,
inline_box_data_3}));
// TODO(nektar): AXTree has a bug whereby it doesn't update the unignored
// cached values when the ignored state is flipped on the root.
GetNodeFromTree(root_data.id)->UpdateUnignoredCachedValues();
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->IsIgnored());
test_position = text_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_1.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(
GetTreeID(), root_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->IsIgnored());
// Changing the adjustment behavior should not change the outcome.
test_position = text_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_1.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), root_data.id,
1 /* child_index */);
EXPECT_TRUE(tree_position->IsIgnored());
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// Changing the adjustment behavior should not affect the outcome.
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// "After children" position.
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), root_data.id,
2 /* child_index */);
EXPECT_TRUE(tree_position->IsIgnored());
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// Changing the adjustment behavior should not affect the outcome.
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// "Before children" position.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), container_data.id, 0 /* child_index */);
EXPECT_TRUE(tree_position->IsIgnored());
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// "After children" position.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), container_data.id, 1 /* child_index */);
EXPECT_TRUE(tree_position->IsIgnored());
// Changing the adjustment behavior should not affect the outcome.
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_data.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->IsIgnored());
test_position = text_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_3.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(
GetTreeID(), inline_box_data_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->IsIgnored());
test_position = text_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_3.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(
GetTreeID(), inline_box_data_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(text_position->IsIgnored());
test_position = text_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_1.id, test_position->anchor_id());
// This should be an "after text" position.
EXPECT_EQ(1, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_data_2.id, AXNodePosition::BEFORE_TEXT);
EXPECT_TRUE(tree_position->IsIgnored());
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveForward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
test_position = tree_position->AsUnignoredPosition(
AXPositionAdjustmentBehavior::kMoveBackward);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box_data_1.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
}
TEST_F(AXPositionTest, CreatePositionAtTextBoundaryContentStartEndIsIgnored) {
// +-root_data
// +-static_text_data_1
// | +-inline_box_data_1 IGNORED
// +-static_text_data_2
// | +-inline_box_data_2
// +-static_text_data_3
// | +-inline_box_data_3
// +-static_text_data_4
// +-inline_box_data_4 IGNORED
constexpr AXNodeID ROOT_ID = 1;
constexpr AXNodeID STATIC_TEXT1_ID = 2;
constexpr AXNodeID STATIC_TEXT2_ID = 3;
constexpr AXNodeID STATIC_TEXT3_ID = 4;
constexpr AXNodeID STATIC_TEXT4_ID = 5;
constexpr AXNodeID INLINE_BOX1_ID = 6;
constexpr AXNodeID INLINE_BOX2_ID = 7;
constexpr AXNodeID INLINE_BOX3_ID = 8;
constexpr AXNodeID INLINE_BOX4_ID = 9;
AXNodeData root_data;
root_data.id = ROOT_ID;
root_data.role = ax::mojom::Role::kRootWebArea;
AXNodeData static_text_data_1;
static_text_data_1.id = STATIC_TEXT1_ID;
static_text_data_1.role = ax::mojom::Role::kStaticText;
static_text_data_1.SetName("One");
AXNodeData inline_box_data_1;
inline_box_data_1.id = INLINE_BOX1_ID;
inline_box_data_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_1.SetName("One");
inline_box_data_1.AddState(ax::mojom::State::kIgnored);
inline_box_data_1.AddIntListAttribute(
ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
inline_box_data_1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{3});
inline_box_data_1.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
INLINE_BOX2_ID);
AXNodeData static_text_data_2;
static_text_data_2.id = STATIC_TEXT2_ID;
static_text_data_2.role = ax::mojom::Role::kStaticText;
static_text_data_2.SetName("Two");
AXNodeData inline_box_data_2;
inline_box_data_2.id = INLINE_BOX2_ID;
inline_box_data_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_2.SetName("Two");
inline_box_data_2.AddIntListAttribute(
ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
inline_box_data_2.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{3});
inline_box_data_2.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
INLINE_BOX1_ID);
inline_box_data_2.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
INLINE_BOX3_ID);
AXNodeData static_text_data_3;
static_text_data_3.id = STATIC_TEXT3_ID;
static_text_data_3.role = ax::mojom::Role::kStaticText;
static_text_data_3.SetName("Three");
AXNodeData inline_box_data_3;
inline_box_data_3.id = INLINE_BOX3_ID;
inline_box_data_3.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_3.SetName("Three");
inline_box_data_3.AddIntListAttribute(
ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
inline_box_data_3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{5});
inline_box_data_3.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
INLINE_BOX2_ID);
inline_box_data_3.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
INLINE_BOX4_ID);
AXNodeData static_text_data_4;
static_text_data_4.id = STATIC_TEXT4_ID;
static_text_data_4.role = ax::mojom::Role::kStaticText;
static_text_data_4.SetName("Four");
AXNodeData inline_box_data_4;
inline_box_data_4.id = INLINE_BOX4_ID;
inline_box_data_4.role = ax::mojom::Role::kInlineTextBox;
inline_box_data_4.SetName("Four");
inline_box_data_4.AddState(ax::mojom::State::kIgnored);
inline_box_data_3.AddIntListAttribute(
ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
inline_box_data_3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{4});
inline_box_data_3.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
INLINE_BOX3_ID);
root_data.child_ids = {static_text_data_1.id, static_text_data_2.id,
static_text_data_3.id, static_text_data_4.id};
static_text_data_1.child_ids = {inline_box_data_1.id};
static_text_data_2.child_ids = {inline_box_data_2.id};
static_text_data_3.child_ids = {inline_box_data_3.id};
static_text_data_4.child_ids = {inline_box_data_4.id};
SetTree(
CreateAXTree({root_data, static_text_data_1, static_text_data_2,
static_text_data_3, static_text_data_4, inline_box_data_1,
inline_box_data_2, inline_box_data_3, inline_box_data_4}));
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_data_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(text_position->IsIgnored());
TestPositionType test_position = text_position->CreatePositionAtTextBoundary(
ax::mojom::TextBoundary::kWordStart, ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = text_position->CreatePositionAtTextBoundary(
ax::mojom::TextBoundary::kWordStart, ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_2.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(
GetTreeID(), inline_box_data_3.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(text_position->IsIgnored());
test_position = text_position->CreatePositionAtTextBoundary(
ax::mojom::TextBoundary::kWordStart, ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_3.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = text_position->CreatePositionAtTextBoundary(
ax::mojom::TextBoundary::kWordStart, ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box_data_2.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, CreatePositionAtInvalidGraphemeBoundary) {
std::vector<int> text_offsets;
SetTree(CreateMultilingualDocument(&text_offsets));
TestPositionType test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(4, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 10 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(10, test_position->text_offset());
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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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();
ASSERT_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(
GetTreeID(), 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(
GetTreeID(), 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(GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), 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(
GetTreeID(), static_text1_.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
TestPositionType test_position =
tree_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(static_text1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
// 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(button_.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
// StopAtLastAnchorBoundary should stop at the start of the whole content
// while CrossBoundary should return a null position when crossing it.
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtPreviousFormatStartWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
// 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());
// StopAtLastAnchorBoundary should stop at the start of the whole content
// while CrossBoundary should return a null position when crossing it.
test_position = test_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
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(
GetTreeID(), button_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
TestPositionType test_position = tree_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// 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->IsTreePosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
// StopAtLastAnchorBoundary should stop at the end of the whole content while
// CrossBoundary should return a null position when crossing it.
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
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(
GetTreeID(), button_.id, 0 /* 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(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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::StopAtLastAnchorBoundary);
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());
// 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_box2_.id, test_position->anchor_id());
EXPECT_EQ(6, test_position->text_offset());
// StopAtLastAnchorBoundary should stop at the end of the whole content while
// CrossBoundary should return a null position when crossing it.
test_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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_position = test_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
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.
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};
SetTree(CreateAXTree({root_data, text_data, more_text_data}));
// Test CreatePreviousFormatStartPosition at the start of the whole content.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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 whole content.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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());
}
TEST_F(AXPositionTest, MoveByFormatWithIgnoredNodes) {
// ++1 kRootWebArea
// ++++2 kGenericContainer
// ++++++3 kButton
// ++++++++4 kStaticText
// ++++++++++5 kInlineTextBox
// ++++++++6 kSvgRoot ignored
// ++++++++++7 kGenericContainer ignored
// ++++8 kGenericContainer
// ++++++9 kHeading
// ++++++++10 kStaticText
// ++++++++++11 kInlineTextBox
// ++++++12 kStaticText
// ++++++++13 kInlineTextBox
// ++++++14 kGenericContainer ignored
// ++++15 kGenericContainer
// ++++++16 kHeading
// ++++++++17 kStaticText
// ++++++++++18 kInlineTextBox
// ++++19 kGenericContainer
// ++++++20 kGenericContainer ignored
// ++++++21 kStaticText
// ++++++++22 kInlineTextBox
// ++++++23 kHeading
// ++++++++24 kStaticText
// ++++++++++25 kInlineTextBox
AXNodeData root_1;
AXNodeData generic_container_2;
AXNodeData button_3;
AXNodeData static_text_4;
AXNodeData inline_box_5;
AXNodeData svg_root_6;
AXNodeData generic_container_7;
AXNodeData generic_container_8;
AXNodeData heading_9;
AXNodeData static_text_10;
AXNodeData inline_box_11;
AXNodeData static_text_12;
AXNodeData inline_box_13;
AXNodeData generic_container_14;
AXNodeData generic_container_15;
AXNodeData heading_16;
AXNodeData static_text_17;
AXNodeData inline_box_18;
AXNodeData generic_container_19;
AXNodeData generic_container_20;
AXNodeData static_text_21;
AXNodeData inline_box_22;
AXNodeData heading_23;
AXNodeData static_text_24;
AXNodeData inline_box_25;
root_1.id = 1;
generic_container_2.id = 2;
button_3.id = 3;
static_text_4.id = 4;
inline_box_5.id = 5;
svg_root_6.id = 6;
generic_container_7.id = 7;
generic_container_8.id = 8;
heading_9.id = 9;
static_text_10.id = 10;
inline_box_11.id = 11;
static_text_12.id = 12;
inline_box_13.id = 13;
generic_container_14.id = 14;
generic_container_15.id = 15;
heading_16.id = 16;
static_text_17.id = 17;
inline_box_18.id = 18;
generic_container_19.id = 19;
generic_container_20.id = 20;
static_text_21.id = 21;
inline_box_22.id = 22;
heading_23.id = 23;
static_text_24.id = 24;
inline_box_25.id = 25;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {generic_container_2.id, generic_container_8.id,
generic_container_15.id, generic_container_19.id};
generic_container_2.role = ax::mojom::Role::kGenericContainer;
generic_container_2.child_ids = {button_3.id};
button_3.role = ax::mojom::Role::kButton;
button_3.child_ids = {static_text_4.id, svg_root_6.id};
static_text_4.role = ax::mojom::Role::kStaticText;
static_text_4.child_ids = {inline_box_5.id};
static_text_4.SetName("Button");
inline_box_5.role = ax::mojom::Role::kInlineTextBox;
inline_box_5.SetName("Button");
svg_root_6.role = ax::mojom::Role::kSvgRoot;
svg_root_6.child_ids = {generic_container_7.id};
svg_root_6.AddState(ax::mojom::State::kIgnored);
generic_container_7.role = ax::mojom::Role::kGenericContainer;
generic_container_7.AddState(ax::mojom::State::kIgnored);
generic_container_8.role = ax::mojom::Role::kGenericContainer;
generic_container_8.child_ids = {heading_9.id, static_text_12.id,
generic_container_14.id};
heading_9.role = ax::mojom::Role::kHeading;
heading_9.child_ids = {static_text_10.id};
static_text_10.role = ax::mojom::Role::kStaticText;
static_text_10.child_ids = {inline_box_11.id};
static_text_10.SetName("Heading");
inline_box_11.role = ax::mojom::Role::kInlineTextBox;
inline_box_11.SetName("Heading");
static_text_12.role = ax::mojom::Role::kStaticText;
static_text_12.child_ids = {inline_box_13.id};
static_text_12.SetName("3.14");
inline_box_13.role = ax::mojom::Role::kInlineTextBox;
inline_box_13.SetName("3.14");
generic_container_14.role = ax::mojom::Role::kGenericContainer;
generic_container_14.AddState(ax::mojom::State::kIgnored);
generic_container_15.role = ax::mojom::Role::kGenericContainer;
generic_container_15.child_ids = {heading_16.id};
heading_16.role = ax::mojom::Role::kHeading;
heading_16.child_ids = {static_text_17.id};
static_text_17.role = ax::mojom::Role::kStaticText;
static_text_17.child_ids = {inline_box_18.id};
static_text_17.SetName("Heading");
inline_box_18.role = ax::mojom::Role::kInlineTextBox;
inline_box_18.SetName("Heading");
generic_container_19.role = ax::mojom::Role::kGenericContainer;
generic_container_19.child_ids = {generic_container_20.id, static_text_21.id,
heading_23.id};
generic_container_20.role = ax::mojom::Role::kGenericContainer;
generic_container_20.AddState(ax::mojom::State::kIgnored);
static_text_21.role = ax::mojom::Role::kStaticText;
static_text_21.child_ids = {inline_box_22.id};
static_text_21.SetName("3.14");
inline_box_22.role = ax::mojom::Role::kInlineTextBox;
inline_box_22.SetName("3.14");
heading_23.role = ax::mojom::Role::kHeading;
heading_23.child_ids = {static_text_24.id};
static_text_24.role = ax::mojom::Role::kStaticText;
static_text_24.child_ids = {inline_box_25.id};
static_text_24.SetName("Heading");
inline_box_25.role = ax::mojom::Role::kInlineTextBox;
inline_box_25.SetName("Heading");
SetTree(CreateAXTree({root_1,
generic_container_2,
button_3,
static_text_4,
inline_box_5,
svg_root_6,
generic_container_7,
generic_container_8,
heading_9,
static_text_10,
inline_box_11,
static_text_12,
inline_box_13,
generic_container_14,
generic_container_15,
heading_16,
static_text_17,
inline_box_18,
generic_container_19,
generic_container_20,
static_text_21,
inline_box_22,
heading_23,
static_text_24,
inline_box_25}));
// There are two major cases to consider for format boundaries with ignored
// nodes:
// Case 1: When the ignored node is directly next to the current position.
// Case 2: When the ignored node is directly next to the next/previous format
// boundary.
// Case 1
// This test case spans nodes 2 to 11, inclusively.
{
// Forward movement
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_5.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(inline_box_5.id, text_position->anchor_id());
EXPECT_EQ(6, text_position->text_offset());
text_position = text_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(inline_box_11.id, text_position->anchor_id());
EXPECT_EQ(7, text_position->text_offset());
// Backward movement
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_11.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(inline_box_11.id, text_position->anchor_id());
EXPECT_EQ(0, text_position->text_offset());
text_position = text_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(inline_box_5.id, text_position->anchor_id());
EXPECT_EQ(0, text_position->text_offset());
}
// Case 2
// This test case spans nodes 8 to 25.
{
// Forward movement
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_11.id, 7 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(inline_box_11.id, text_position->anchor_id());
EXPECT_EQ(7, text_position->text_offset());
text_position = text_position->CreateNextFormatEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(inline_box_13.id, text_position->anchor_id());
EXPECT_EQ(4, text_position->text_offset());
// Backward movement
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_25.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(inline_box_25.id, text_position->anchor_id());
EXPECT_EQ(0, text_position->text_offset());
text_position = text_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(inline_box_22.id, text_position->anchor_id());
EXPECT_EQ(0, text_position->text_offset());
}
}
TEST_F(AXPositionTest, CreatePositionAtPageBoundaryWithNullPosition) {
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, CreatePositionAtPageBoundaryWithTreePosition) {
AXNodeData root_data, page_1_data, page_1_text_data, page_2_data,
page_2_text_data, page_3_data, page_3_text_data;
SetTree(CreateMultipageDocument(root_data, page_1_data, page_1_text_data,
page_2_data, page_2_text_data, page_3_data,
page_3_text_data));
// Test CreateNextPageStartPosition at the start of the whole content.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), page_1_data.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
// StopIfAlreadyAtBoundary shouldn't move at all since it's at a boundary.
TestPositionType test_position = tree_position->CreateNextPageStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(page_1_data.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
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 = tree_position->CreateNextPageStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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 CreateNextPageEndPosition until the end of content is reached.
test_position = tree_position->CreateNextPageEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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_position = test_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());
test_position = test_position->CreateNextPageEndPosition(
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());
// StopAtLastAnchorBoundary shouldn't move past the end of the whole content.
test_position = test_position->CreateNextPageStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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::StopAtLastAnchorBoundary);
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 accessibility tree.
tree_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->IsTreePosition());
EXPECT_EQ(page_3_text_data.id, tree_position->anchor_id());
EXPECT_EQ(0, tree_position->child_index());
test_position = tree_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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 = tree_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->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::StopAtLastAnchorBoundary);
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());
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());
// StopAtLastAnchorBoundary shouldn't move past the start of the whole
// content.
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
test_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
}
TEST_F(AXPositionTest, CreatePositionAtPageBoundaryWithTextPosition) {
AXNodeData root_data, page_1_data, page_1_text_data, page_2_data,
page_2_text_data, page_3_data, page_3_text_data;
SetTree(CreateMultipageDocument(root_data, page_1_data, page_1_text_data,
page_2_data, page_2_text_data, page_3_data,
page_3_text_data));
// Test CreateNextPageStartPosition at the start of the whole content.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), page_1_text_data.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
// StopIfAlreadyAtBoundary shouldn't move at all since it's at a boundary.
TestPositionType test_position = text_position->CreateNextPageStartPosition(
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());
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 = text_position->CreateNextPageStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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 CreateNextPageEndPosition until the end of content is reached.
test_position = test_position->CreateNextPageEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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->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());
// StopAtLastAnchorBoundary shouldn't move past the end of the whole content.
test_position = test_position->CreateNextPageStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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::StopAtLastAnchorBoundary);
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 accessibility tree.
text_position = test_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(page_3_text_data.id, text_position->anchor_id());
EXPECT_EQ(24, text_position->text_offset());
test_position = text_position->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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 = text_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->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::StopAtLastAnchorBoundary);
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());
// StopAtLastAnchorBoundary shouldn't move past the start of the whole
// content.
test_position = test_position->CreatePreviousPageStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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->CreatePreviousPageEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
}
TEST_F(AXPositionTest, CreatePositionAtPageBoundaryWithNonPaginatedDocument) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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 whole content 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 whole content, 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
// whole content.
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());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfAXTreeWithNullPosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position =
null_position->CreatePositionAtStartOfAXTree();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
EXPECT_FALSE(test_position->AtStartOfAXTree());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfAXTreeWithTreePosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
const AXTreeID views_tree_id = trees[0].GetTree()->GetAXTreeID();
const AXTreeID webpage_tree_id = trees[1].GetTree()->GetAXTreeID();
const AXTreeID iframe_tree_id = trees[2].GetTree()->GetAXTreeID();
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, window.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position =
tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, window.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, back_button.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, back_button.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, web_view.id,
0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, web_view.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, root_web_area.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, root_web_area.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, paragraph.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, paragraph.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
iframe_tree_id, iframe_root.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(iframe_tree_id, test_position->tree_id());
EXPECT_EQ(iframe_root.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
iframe_tree_id, iframe_root.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(iframe_tree_id, test_position->tree_id());
EXPECT_EQ(iframe_root.id, test_position->anchor_id());
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfAXTreeWithTextPosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
const AXTreeID views_tree_id = trees[0].GetTree()->GetAXTreeID();
const AXTreeID webpage_tree_id = trees[1].GetTree()->GetAXTreeID();
const AXTreeID iframe_tree_id = trees[2].GetTree()->GetAXTreeID();
TestPositionType text_position = AXNodePosition::CreateTextPosition(
views_tree_id, window.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
TestPositionType test_position =
text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, window.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, back_button.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, back_button.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, web_view.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, web_view.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, root_web_area.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, root_web_area.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, paragraph.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, paragraph.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
iframe_tree_id, iframe_root.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(iframe_tree_id, test_position->tree_id());
EXPECT_EQ(iframe_root.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
iframe_tree_id, iframe_root.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfAXTree());
EXPECT_EQ(iframe_tree_id, test_position->tree_id());
EXPECT_EQ(iframe_root.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfAXTreeWithNullPosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreatePositionAtEndOfAXTree();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
EXPECT_FALSE(test_position->AtEndOfAXTree());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfAXTreeWithTreePosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
const AXTreeID views_tree_id = trees[0].GetTree()->GetAXTreeID();
const AXTreeID webpage_tree_id = trees[1].GetTree()->GetAXTreeID();
const AXTreeID iframe_tree_id = trees[2].GetTree()->GetAXTreeID();
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, window.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, window.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, back_button.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, back_button.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, web_view.id,
0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, web_view.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, root_web_area.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, root_web_area.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, paragraph.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, paragraph.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
iframe_tree_id, iframe_root.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(iframe_tree_id, test_position->tree_id());
EXPECT_EQ(iframe_root.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
iframe_tree_id, iframe_root.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(iframe_tree_id, test_position->tree_id());
EXPECT_EQ(iframe_root.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfAXTreeWithTextPosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
const AXTreeID views_tree_id = trees[0].GetTree()->GetAXTreeID();
const AXTreeID webpage_tree_id = trees[1].GetTree()->GetAXTreeID();
const AXTreeID iframe_tree_id = trees[2].GetTree()->GetAXTreeID();
TestPositionType text_position = AXNodePosition::CreateTextPosition(
views_tree_id, window.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
TestPositionType test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, window.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, back_button.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, back_button.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, web_view.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, web_view.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, root_web_area.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, root_web_area.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, paragraph.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, paragraph.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
iframe_tree_id, iframe_root.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(iframe_tree_id, test_position->tree_id());
EXPECT_EQ(iframe_root.id, test_position->anchor_id());
EXPECT_EQ(13, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
iframe_tree_id, iframe_root.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfAXTree();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfAXTree());
EXPECT_EQ(iframe_tree_id, test_position->tree_id());
EXPECT_EQ(iframe_root.id, test_position->anchor_id());
EXPECT_EQ(13, test_position->text_offset());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfContentWithNullPosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position =
null_position->CreatePositionAtStartOfContent();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfContentWithTreePosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
const AXTreeID views_tree_id = trees[0].GetTree()->GetAXTreeID();
const AXTreeID webpage_tree_id = trees[1].GetTree()->GetAXTreeID();
const AXTreeID iframe_tree_id = trees[2].GetTree()->GetAXTreeID();
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, window.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position =
tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, window.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, back_button.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, back_button.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, web_view.id,
0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, web_view.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, root_web_area.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, root_web_area.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, paragraph.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, paragraph.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
iframe_tree_id, iframe_root.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
iframe_tree_id, iframe_root.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
}
TEST_F(AXPositionTest, CreatePositionAtStartOfContentWithTextPosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
const AXTreeID views_tree_id = trees[0].GetTree()->GetAXTreeID();
const AXTreeID webpage_tree_id = trees[1].GetTree()->GetAXTreeID();
const AXTreeID iframe_tree_id = trees[2].GetTree()->GetAXTreeID();
TestPositionType text_position = AXNodePosition::CreateTextPosition(
views_tree_id, window.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
TestPositionType test_position =
text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, window.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, back_button.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, back_button.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, web_view.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, web_view.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(window.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, root_web_area.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, root_web_area.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, paragraph.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, paragraph.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
iframe_tree_id, iframe_root.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
text_position = AXNodePosition::CreateTextPosition(
iframe_tree_id, iframe_root.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtStartOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtStartOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(root_web_area.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfContentWithNullPosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position =
null_position->CreatePositionAtEndOfContent();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfContentWithTreePosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
const AXTreeID views_tree_id = trees[0].GetTree()->GetAXTreeID();
const AXTreeID webpage_tree_id = trees[1].GetTree()->GetAXTreeID();
const AXTreeID iframe_tree_id = trees[2].GetTree()->GetAXTreeID();
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, window.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position =
tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, window.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, back_button.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
views_tree_id, back_button.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, web_view.id,
0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(views_tree_id, web_view.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, root_web_area.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, root_web_area.id, 1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, paragraph.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
webpage_tree_id, paragraph.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
iframe_tree_id, iframe_root.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
iframe_tree_id, iframe_root.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->child_index());
}
TEST_F(AXPositionTest, CreatePositionAtEndOfContentWithTextPosition) {
// Create three accessibility trees as follows:
//
// Window (First tree)
// ++NonClientView
// ++++BrowserView
// ++++++ToolbarView
// ++++++++kButton name="Back"
// ++++WebView
// ++++++kRootWebArea (Second tree)
// ++++++++kIframe
// ++++++++++kRootWebArea name="Inside iframe" (Third tree)
// ++++++++kParagraph name="After iframe"
// ++++TextField (Address bar - part of first tree.)
AXNodeData window, back_button, web_view, root_web_area, iframe_root,
paragraph, address_bar;
std::vector<TestAXTreeManager> trees;
ASSERT_NO_FATAL_FAILURE(CreateBrowserWindow(window, back_button, web_view,
root_web_area, iframe_root,
paragraph, address_bar, trees));
const AXTreeID views_tree_id = trees[0].GetTree()->GetAXTreeID();
const AXTreeID webpage_tree_id = trees[1].GetTree()->GetAXTreeID();
const AXTreeID iframe_tree_id = trees[2].GetTree()->GetAXTreeID();
TestPositionType text_position = AXNodePosition::CreateTextPosition(
views_tree_id, window.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
TestPositionType test_position =
text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, window.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, back_button.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, back_button.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, web_view.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
views_tree_id, web_view.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(views_tree_id, test_position->tree_id());
EXPECT_EQ(address_bar.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, root_web_area.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, root_web_area.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, paragraph.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
webpage_tree_id, paragraph.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
iframe_tree_id, iframe_root.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
iframe_tree_id, iframe_root.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreatePositionAtEndOfContent();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_TRUE(test_position->AtEndOfContent());
EXPECT_EQ(webpage_tree_id, test_position->tree_id());
EXPECT_EQ(paragraph.id, test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
}
TEST_F(AXPositionTest, CreateChildPositionAtWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreateChildPositionAt(0);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreateChildPositionAtWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 2 /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->CreateChildPositionAt(1);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
// Since the anchor is a leaf node, |child_index| should signify that this is
// a "before text" position.
EXPECT_EQ(AXNodePosition::BEFORE_TEXT, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), button_.id,
0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreateChildPositionAt(0);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreateChildPositionAtWithTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text1_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->CreateChildPositionAt(0);
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text2_.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreateChildPositionAt(1);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreateParentPositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreateParentPositionWithTreePosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), check_box_.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType test_position = tree_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(root_.id, test_position->anchor_id());
// |child_index| should point to the check box node because the original
// position was a "before text" position on the check box.
EXPECT_EQ(1, test_position->child_index());
// Create a position that points at the end of the first line, right after the
// check box: an "after text" position on the check box.
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), check_box_.id,
0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(root_.id, test_position->anchor_id());
// |child_index| should point to after the check box node because the original
// position was an "after text" position.
EXPECT_EQ(2, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
1 /* child_index */);
ASSERT_NE(nullptr, tree_position);
test_position = tree_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition())
<< "We should cross into a minimalistic Views tree.";
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box2_.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
test_position = tree_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(static_text2_.id, test_position->anchor_id());
// A "before text" position on the inline text box should result in a "before
// children" position on the static text parent.
EXPECT_EQ(0, test_position->child_index());
test_position = test_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
EXPECT_EQ(2, test_position->child_index());
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box2_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ASSERT_TRUE(tree_position->IsTreePosition());
test_position = tree_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(static_text2_.id, test_position->anchor_id());
// An "After text" position on the inline text box should result in an "after
// children" position on the static text parent.
EXPECT_EQ(1, test_position->child_index());
test_position = test_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
EXPECT_EQ(3, test_position->child_index());
}
TEST_F(AXPositionTest, CreateParentPositionWithTextPosition) {
// Create a position that points at the end of the first line, right after the
// check box.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(root_.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(
GetTreeID(), root_.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
test_position = text_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition())
<< "We should cross into a minimalistic Views tree.";
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(static_text2_.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = test_position->CreateParentPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
// |text_offset| should point to the same offset on the second line where the
// static text node position was pointing at.
EXPECT_EQ(12, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, CreateParentPositionWithMoveDirection) {
// This test only applies when "object replacement characters" are used in the
// accessibility tree, e.g., in IAccessible2, UI Automation and Linux ATK
// APIs.
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// This test ensures that "CreateParentPosition" (and by extension
// "CreateAncestorPosition") works correctly when it is given either a tree or
// a text position whose parent position is inside an "object replacement
// character". The resulting parent position should be either before or after
// the "object replacement character", based on the provided move direction.
//
// Nodes represented by an embedded object character, such as a link, a
// paragraph, a text field or a check box, may create an ambiguity as to where
// the parent position should be located. For example, look at the following
// accessibility tree.
//
// ++1 kRootWebArea isLineBreakingObject
// ++++2 kLink "<embedded_object>"
// ++++++3 kStaticText "Hello"
// ++++++++4 kInlineTextBox "hello"
// ++++++5 kParagraph "<embedded_object>"
// ++++++++6 kStaticText "world."
// ++++++++++7 kInlineTextBox "world."
//
// The parent position of a text position inside the inline text box with the
// word "world", may either be before or after the paragraph. They are both
// equally valid and the choice depends on which navigation operation we are
// trying to accomplish, e.g. move to the start of the line vs. the end.
AXNodeData root_1;
AXNodeData link_2;
AXNodeData static_text_3;
AXNodeData inline_box_4;
AXNodeData paragraph_5;
AXNodeData static_text_6;
AXNodeData inline_box_7;
root_1.id = 1;
link_2.id = 2;
static_text_3.id = 3;
inline_box_4.id = 4;
paragraph_5.id = 5;
static_text_6.id = 6;
inline_box_7.id = 7;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {link_2.id};
root_1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
link_2.role = ax::mojom::Role::kLink;
link_2.child_ids = {static_text_3.id, paragraph_5.id};
static_text_3.role = ax::mojom::Role::kStaticText;
static_text_3.child_ids = {inline_box_4.id};
static_text_3.SetName("Hello");
inline_box_4.role = ax::mojom::Role::kInlineTextBox;
inline_box_4.SetName("Hello");
paragraph_5.role = ax::mojom::Role::kParagraph;
paragraph_5.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph_5.child_ids = {static_text_6.id};
static_text_6.role = ax::mojom::Role::kStaticText;
static_text_6.child_ids = {inline_box_7.id};
static_text_6.SetName("world.");
inline_box_7.role = ax::mojom::Role::kInlineTextBox;
inline_box_7.SetName("world.");
SetTree(CreateAXTree({root_1, link_2, static_text_3, inline_box_4,
paragraph_5, static_text_6, inline_box_7}));
//
// Tree positions.
//
// Find the equivalent position on the root, when the original position is
// before "Hello", with a forward direction.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_4.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
TestPositionType ancestor_position = tree_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kForward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTreePosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The child index should be before the "object replacement character" for the
// link in the root's text, because the original index was before "Hello",
// i.e., before all the text contained in the link. The move direction should
// not matter.
EXPECT_EQ(0, ancestor_position->child_index());
// Find the equivalent position on the root, when the original position is
// before "Hello", with a backward direction.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_4.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, tree_position);
ancestor_position = tree_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kBackward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTreePosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The child index should be before the "object replacement character" for the
// link in the root's text, because the original index was before "Hello",
// i.e., before all the text contained in the link. The move direction should
// not matter.
EXPECT_EQ(0, ancestor_position->child_index());
// Find the equivalent position on the root, when the original position is
// after "Hello", with a forward direction.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_4.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ancestor_position = tree_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kForward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTreePosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The child index should be after the "object replacement character" for the
// link in the root's text, because the original index was after "Hello",
// i.e., in the middle of the link's text, and the direction was forward.
EXPECT_EQ(1, ancestor_position->child_index());
// Find the equivalent position on the root, when the original position is
// after "Hello", with a backward direction.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_4.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ancestor_position = tree_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kBackward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTreePosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The child index should be before the "object replacement character" for the
// link in the root's text, because even though the original index was after
// "Hello" the direction was backward.
EXPECT_EQ(0, ancestor_position->child_index());
// Find the equivalent position on the root, when the original position is
// after "world.", with a forward direction.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_7.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ancestor_position = tree_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kForward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTreePosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The child index should be after the "object replacement character" for the
// link in the root's text, because the original index was after "world.",
// i.e., after all of the text in the link. The move direction should not
// matter.
EXPECT_EQ(1, ancestor_position->child_index());
// Find the equivalent position on the root, when the original position is
// after "world.", with a backward direction.
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_7.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position);
ancestor_position = tree_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kBackward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTreePosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The child index should be after the "object replacement character" for the
// link in the root's text, because the original index was after "world.",
// i.e., after all of the text in the link. The move direction should not
// matter.
EXPECT_EQ(1, ancestor_position->child_index());
//
// Text positions.
//
// Find the equivalent position on the root, when the original position is
// before "Hello", with a forward direction.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_4.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ancestor_position = text_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kForward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTextPosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The text offset should be before the "object replacement character" for the
// link in the root's text, because the original offset was before "Hello",
// i.e., before all the text contained in the link. The move direction should
// not matter.
EXPECT_EQ(0, ancestor_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream,
ancestor_position->affinity());
// Find the equivalent position on the root, when the original position is
// before "Hello", with a backward direction.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_4.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ancestor_position = text_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kBackward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTextPosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The text offset should be before the "object replacement character" for the
// link in the root's text, because the original offset was before "Hello",
// i.e., before all the text contained in the link. The move direction should
// not matter.
EXPECT_EQ(0, ancestor_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream,
ancestor_position->affinity());
// Find the equivalent position on the root, when the original position is
// after "Hello", with a forward direction.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_4.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ancestor_position = text_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kForward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTextPosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The text offset should be after the "object replacement character" for the
// link in the root's text, because the original offset was after "Hello" and
// the move direction was forward.
EXPECT_EQ(1, ancestor_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream,
ancestor_position->affinity());
// Find the equivalent position on the root, when the original position is
// after "Hello", with a backward direction.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_4.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ancestor_position = text_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kBackward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTextPosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The text offset should be before the "object replacement character" for the
// link in the root's text, because even though the original offset was after
// "Hello", the move direction was backward.
EXPECT_EQ(0, ancestor_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream,
ancestor_position->affinity());
// Find the equivalent position on the root, when the original position is
// inside "world.", with a forward direction.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_7.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ancestor_position = text_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kForward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTextPosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The text offset should be after the "object replacement character" for the
// link in the root's text, because the original offset was inside "world."
// and the move direction was forward.
EXPECT_EQ(1, ancestor_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream,
ancestor_position->affinity());
// Find the equivalent position on the root, when the original position is
// inside "world.", with a backward direction.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_7.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ancestor_position = text_position->CreateAncestorPosition(
GetRootAsAXNode(), ax::mojom::MoveDirection::kBackward);
ASSERT_NE(nullptr, ancestor_position);
EXPECT_TRUE(ancestor_position->IsTextPosition());
EXPECT_EQ(root_1.id, ancestor_position->anchor_id());
// The text offset should be before the "object replacement character" for the
// link in the root's text, because even though the original offset was inside
// "world.", the move direction was backward.
EXPECT_EQ(0, ancestor_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream,
ancestor_position->affinity());
}
TEST_F(AXPositionTest, CreateParentAndLeafPositionWithIgnoredNodes) {
// The text of ignored nodes should not be visible in the tree's text
// representation, but the text of their unignored children should.
// `AXPosition::CreateParentPosition` should be able to work even when called
// on an ignored position, and it should also be able to produce parent
// positions on ignored nodes that have the correct text offset and affinity.
// `AXPosition::AsLeafTextPosition`, on the other hand, should skip all
// ignored nodes.
//
// Simulate a tree with two lines of text and some ignored nodes between them:
// ++kRootWebArea "HelloWorld"
// ++++kGenericContainer ignored
// ++++++kStaticText "Hello"
// ++++++++kInlineTextBox "Hello"
// ++++kStaticText "Ignored1"
// ++++++kInlineTextBox "Ignored1"
// ++++kStaticText "Ignored2"
// ++++++kInlineTextBox "Ignored2"
// ++++kStaticText "World"
// ++++++kInlineTextBox "World"
AXNodeData root;
AXNodeData generic_container_ignored;
AXNodeData static_text_1;
AXNodeData inline_box_1;
AXNodeData static_text_ignored_1;
AXNodeData inline_box_ignored_1;
AXNodeData static_text_ignored_2;
AXNodeData inline_box_ignored_2;
AXNodeData static_text_2;
AXNodeData inline_box_2;
root.id = 1;
generic_container_ignored.id = 2;
static_text_1.id = 3;
inline_box_1.id = 4;
static_text_2.id = 5;
inline_box_2.id = 6;
static_text_ignored_1.id = 7;
inline_box_ignored_1.id = 8;
static_text_ignored_2.id = 9;
inline_box_ignored_2.id = 10;
root.role = ax::mojom::Role::kRootWebArea;
root.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
root.child_ids = {generic_container_ignored.id, static_text_ignored_1.id,
static_text_ignored_2.id, static_text_2.id};
generic_container_ignored.role = ax::mojom::Role::kGenericContainer;
generic_container_ignored.AddState(ax::mojom::State::kIgnored);
generic_container_ignored.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
generic_container_ignored.child_ids = {static_text_1.id};
static_text_1.role = ax::mojom::Role::kStaticText;
static_text_1.SetName("Hello");
static_text_1.child_ids = {inline_box_1.id};
inline_box_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_1.SetName("Hello");
static_text_ignored_1.role = ax::mojom::Role::kStaticText;
static_text_ignored_1.AddState(ax::mojom::State::kIgnored);
static_text_ignored_1.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
static_text_ignored_1.SetName("Ignored1");
static_text_ignored_1.child_ids = {inline_box_ignored_1.id};
inline_box_ignored_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_ignored_1.AddState(ax::mojom::State::kIgnored);
inline_box_ignored_1.SetName("Ignored1");
static_text_ignored_2.role = ax::mojom::Role::kStaticText;
static_text_ignored_2.AddState(ax::mojom::State::kIgnored);
static_text_ignored_2.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
static_text_ignored_2.SetName("Ignored2");
static_text_ignored_2.child_ids = {inline_box_ignored_2.id};
inline_box_ignored_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_ignored_2.AddState(ax::mojom::State::kIgnored);
inline_box_ignored_2.SetName("Ignored2");
static_text_2.role = ax::mojom::Role::kStaticText;
static_text_2.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
static_text_2.SetName("World");
static_text_2.child_ids = {inline_box_2.id};
inline_box_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_2.SetName("World");
SetTree(CreateAXTree({root, generic_container_ignored, static_text_1,
inline_box_1, static_text_ignored_1,
inline_box_ignored_1, static_text_ignored_2,
inline_box_ignored_2, static_text_2, inline_box_2}));
// "<H>elloWorld"
TestPositionType before_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_root->IsNullPosition());
// "Hello<W>orld"
// On the end of the first line after "Hello".
TestPositionType middle_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(middle_root->IsNullPosition());
// "Hello<W>orld"
// At the start of the second line before "World".
TestPositionType middle_root_upstream = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_FALSE(middle_root_upstream->IsNullPosition());
// "HelloWorld<>"
// Note that since this is the end of content there is no next line after the
// end of the root, so a downstream affinity would still work even though
// technically the position is at the end of the last line.
TestPositionType after_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_root->IsNullPosition());
// "<H>ello"
TestPositionType before_inline_box_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_1->IsNullPosition());
// "Hello<>"
TestPositionType after_inline_box_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_1.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_inline_box_1->IsNullPosition());
// "<I>gnored1"
TestPositionType before_inline_box_ignored_1 =
AXNodePosition::CreateTextPosition(GetTreeID(), inline_box_ignored_1.id,
0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_ignored_1->IsNullPosition());
ASSERT_TRUE(before_inline_box_ignored_1->IsIgnored());
TestPositionType before_inline_box_ignored_1_tree =
AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_ignored_1.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_FALSE(before_inline_box_ignored_1_tree->IsNullPosition());
ASSERT_TRUE(before_inline_box_ignored_1_tree->IsIgnored());
TestPositionType after_inline_box_ignored_1_tree =
AXNodePosition::CreateTreePosition(GetTreeID(), inline_box_ignored_1.id,
0 /* child_index */);
ASSERT_FALSE(after_inline_box_ignored_1_tree->IsNullPosition());
ASSERT_TRUE(after_inline_box_ignored_1_tree->IsIgnored());
// "<I>gnored2"
TestPositionType before_inline_box_ignored_2 =
AXNodePosition::CreateTextPosition(GetTreeID(), inline_box_ignored_2.id,
0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_ignored_2->IsNullPosition());
ASSERT_TRUE(before_inline_box_ignored_2->IsIgnored());
TestPositionType before_inline_box_ignored_2_tree =
AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_ignored_2.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_FALSE(before_inline_box_ignored_2_tree->IsNullPosition());
ASSERT_TRUE(before_inline_box_ignored_2_tree->IsIgnored());
TestPositionType after_inline_box_ignored_2_tree =
AXNodePosition::CreateTreePosition(GetTreeID(), inline_box_ignored_2.id,
0 /* child_index */);
ASSERT_FALSE(after_inline_box_ignored_2_tree->IsNullPosition());
ASSERT_TRUE(after_inline_box_ignored_2_tree->IsIgnored());
// "<W>orld"
TestPositionType before_inline_box_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_2->IsNullPosition());
// "World<>"
TestPositionType after_inline_box_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_2.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_inline_box_2->IsNullPosition());
TestPositionType parent_position =
before_inline_box_1->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(generic_container_ignored.id, parent_position->anchor_id());
EXPECT_EQ(0, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
TestPositionType leaf_position = before_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_1.id, leaf_position->anchor_id());
EXPECT_EQ(0, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
// `inline_box_1` is on a different line from `inline_box_2`, hence the
// equivalent position on the root should have an upstream affinity, despite
// the fact that the intermitiary parent position is on an ignored generic
// container.
parent_position =
after_inline_box_1->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsIgnored());
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(generic_container_ignored.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
// Move one more level up to get to the root.
parent_position = parent_position->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_FALSE(parent_position->IsIgnored());
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, parent_position->affinity());
leaf_position = middle_root_upstream->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_1.id, leaf_position->anchor_id());
EXPECT_EQ(5, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
// By design, positions on ignored nodes between the two lines will be
// considered as part of the previous line when finding the unignored root
// equivalent position.
parent_position = before_inline_box_ignored_1->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, parent_position->affinity());
parent_position = before_inline_box_ignored_1_tree->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(1, parent_position->child_index());
parent_position = after_inline_box_ignored_1_tree->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(2, parent_position->child_index());
parent_position = before_inline_box_ignored_2->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, parent_position->affinity());
parent_position = before_inline_box_ignored_2_tree->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(2, parent_position->child_index());
parent_position = after_inline_box_ignored_2_tree->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(3, parent_position->child_index());
// `inline_box_2` is on the next line, hence the root equivalent position
// should have a downstream affinity.
parent_position =
before_inline_box_2->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
leaf_position = middle_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_2.id, leaf_position->anchor_id());
EXPECT_EQ(0, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
parent_position =
after_inline_box_2->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(10, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
leaf_position = after_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_2.id, leaf_position->anchor_id());
EXPECT_EQ(5, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
}
TEST_F(AXPositionTest, CreateParentAndLeafPositionWithEmptyNodes) {
// `AXPosition::CreateParentPosition` should be able to work even when called
// on a position that is anchored to a node with no text in it, such as a
// button with no value or inner text. Similarly,
// `AXPosition::AsLeafTextPosition` should not skip any empty nodes.
//
// Simulate a tree with two lines of text and some empty nodes between them:
// ++kRootWebArea "HelloWorld"
// ++++kLink "Hello"
// ++++++kStaticText "Hello"
// ++++++++kInlineTextBox "Hello"
// ++++kStaticText ""
// ++++++kInlineTextBox ""
// ++++kButton (empty)
// ++++kStaticText "World"
// ++++++kInlineTextBox "World"
AXNodeData root;
AXNodeData link;
AXNodeData static_text_1;
AXNodeData inline_box_1;
AXNodeData static_text_empty;
AXNodeData inline_box_empty;
AXNodeData button_empty;
AXNodeData static_text_2;
AXNodeData inline_box_2;
root.id = 1;
link.id = 2;
static_text_1.id = 3;
inline_box_1.id = 4;
static_text_empty.id = 5;
inline_box_empty.id = 6;
button_empty.id = 7;
static_text_2.id = 8;
inline_box_2.id = 9;
root.role = ax::mojom::Role::kRootWebArea;
root.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
root.child_ids = {link.id, static_text_empty.id, button_empty.id,
static_text_2.id};
link.role = ax::mojom::Role::kLink;
link.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
link.child_ids = {static_text_1.id};
static_text_1.role = ax::mojom::Role::kStaticText;
static_text_1.SetName("Hello");
static_text_1.child_ids = {inline_box_1.id};
inline_box_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_1.SetName("Hello");
static_text_empty.role = ax::mojom::Role::kStaticText;
static_text_empty.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
static_text_empty.child_ids = {inline_box_empty.id};
inline_box_empty.role = ax::mojom::Role::kInlineTextBox;
button_empty.role = ax::mojom::Role::kButton;
button_empty.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
static_text_2.role = ax::mojom::Role::kStaticText;
static_text_2.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
static_text_2.SetName("World");
static_text_2.child_ids = {inline_box_2.id};
inline_box_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_2.SetName("World");
SetTree(CreateAXTree({root, link, static_text_1, inline_box_1,
static_text_empty, inline_box_empty, button_empty,
static_text_2, inline_box_2}));
// "<H>elloWorld"
TestPositionType before_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_root->IsNullPosition());
// "Hello<W>orld"
TestPositionType middle_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(middle_root->IsNullPosition());
TestPositionType middle_root_upstream = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_FALSE(middle_root_upstream->IsNullPosition());
// "HelloWorld<>"
TestPositionType after_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_root->IsNullPosition());
// "<H>ello"
TestPositionType before_inline_box_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_1->IsNullPosition());
// "Hello<>"
TestPositionType after_inline_box_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_1.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_inline_box_1->IsNullPosition());
TestPositionType before_inline_box_empty = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_empty.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_empty->IsNullPosition());
TestPositionType before_inline_box_empty_tree =
AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_empty.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_FALSE(before_inline_box_empty_tree->IsNullPosition());
TestPositionType after_inline_box_empty_tree =
AXNodePosition::CreateTreePosition(GetTreeID(), inline_box_empty.id,
0 /* child_index */);
ASSERT_FALSE(after_inline_box_empty_tree->IsNullPosition());
TestPositionType before_button_empty = AXNodePosition::CreateTextPosition(
GetTreeID(), button_empty.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_button_empty->IsNullPosition());
TestPositionType before_button_empty_tree =
AXNodePosition::CreateTreePosition(
GetTreeID(), button_empty.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_FALSE(before_button_empty_tree->IsNullPosition());
TestPositionType after_button_empty_tree = AXNodePosition::CreateTreePosition(
GetTreeID(), button_empty.id, 0 /* child_index */);
ASSERT_FALSE(after_button_empty_tree->IsNullPosition());
// "<W>orld"
TestPositionType before_inline_box_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_2->IsNullPosition());
// "World<>"
TestPositionType after_inline_box_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_2.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_inline_box_2->IsNullPosition());
TestPositionType parent_position =
before_inline_box_1->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(link.id, parent_position->anchor_id());
EXPECT_EQ(0, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
TestPositionType leaf_position = before_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_1.id, leaf_position->anchor_id());
EXPECT_EQ(0, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
// `inline_box_1` is on a different line from `inline_box_2`, hence the
// equivalent position on the check box should have had an upstream affinity.
// However, since there are a handful of empty nodes between the check box and
// the second line, those empty nodes form the end of the line, not the check
// box.
parent_position =
after_inline_box_1->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(link.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
leaf_position = middle_root_upstream->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_1.id, leaf_position->anchor_id());
EXPECT_EQ(5, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
// By design, positions on empty nodes between the two lines will be
// considered as part of the previous line when finding the unignored root
// equivalent position.
parent_position =
before_inline_box_empty->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, parent_position->affinity());
parent_position = before_inline_box_empty_tree->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(1, parent_position->child_index());
parent_position = after_inline_box_empty_tree->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(2, parent_position->child_index());
parent_position = before_button_empty->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, parent_position->affinity());
parent_position = before_button_empty_tree->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(2, parent_position->child_index());
parent_position = after_button_empty_tree->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(3, parent_position->child_index());
// `inline_box_2` is on the next line, hence the root equivalent position
// should have a downstream affinity.
parent_position =
before_inline_box_2->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(5, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
leaf_position = middle_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
// Empty nodes should not be skipped when finding the leaf equivalent
// position. (inline_box_empty and not inline_box_2.)
EXPECT_EQ(inline_box_empty.id, leaf_position->anchor_id());
EXPECT_EQ(0, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
parent_position =
after_inline_box_2->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(10, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
leaf_position = after_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_2.id, leaf_position->anchor_id());
EXPECT_EQ(5, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
}
TEST_F(AXPositionTest, CreateParentAndLeafPositionWithEmbeddedObjects) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++kRootWebArea "<embedded>Hello<embedded>"
// ++++kParagraph "Paragraph"
// ++++++kStaticText "Paragraph"
// ++++++++kInlineTextBox "Paragraph"
// ++++kStaticText "Hello"
// ++++++kInlineTextBox "Hello"
// ++++kButton (empty)
AXNodeData root;
AXNodeData paragraph;
AXNodeData static_text_1;
AXNodeData inline_box_1;
AXNodeData static_text_2;
AXNodeData inline_box_2;
AXNodeData button_empty;
root.id = 1;
paragraph.id = 2;
static_text_1.id = 3;
inline_box_1.id = 4;
static_text_2.id = 5;
inline_box_2.id = 6;
button_empty.id = 7;
root.role = ax::mojom::Role::kRootWebArea;
root.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
root.child_ids = {paragraph.id, static_text_2.id, button_empty.id};
paragraph.role = ax::mojom::Role::kParagraph;
paragraph.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph.child_ids = {static_text_1.id};
static_text_1.role = ax::mojom::Role::kStaticText;
static_text_1.SetName("Paragraph");
static_text_1.child_ids = {inline_box_1.id};
inline_box_1.role = ax::mojom::Role::kInlineTextBox;
inline_box_1.SetName("Paragraph");
static_text_2.role = ax::mojom::Role::kStaticText;
static_text_2.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
static_text_2.SetName("Hello");
static_text_2.child_ids = {inline_box_2.id};
inline_box_2.role = ax::mojom::Role::kInlineTextBox;
inline_box_2.SetName("Hello");
button_empty.role = ax::mojom::Role::kButton;
button_empty.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
SetTree(CreateAXTree({root, paragraph, static_text_1, inline_box_1,
static_text_2, inline_box_2, button_empty}));
TestPositionType before_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_root->IsNullPosition());
// The root's first child is an embedded object, i.e. a paragraph. Create two
// positions: one after the paragraph (upstream affinity), and the other
// before the word "Hello" that comes after the paragraph (downstream
// affinity).
TestPositionType middle_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, AXNode::kEmbeddedCharacterLength /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(middle_root->IsNullPosition());
TestPositionType middle_root_upstream = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, AXNode::kEmbeddedCharacterLength /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_FALSE(middle_root_upstream->IsNullPosition());
// The root has 7 characters: two embedded objects and the word "Hello".
TestPositionType after_root = AXNodePosition::CreateTextPosition(
GetTreeID(), root.id, 7 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_root->IsNullPosition());
// "<P>aragraph"
TestPositionType before_inline_box_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_1->IsNullPosition());
// "Paragraph<>"
TestPositionType after_inline_box_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_1.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_inline_box_1->IsNullPosition());
TestPositionType after_inline_box_1_tree = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_1.id, 0 /* child_index */);
ASSERT_FALSE(after_inline_box_1_tree->IsNullPosition());
// "<H>ello"
TestPositionType before_inline_box_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_inline_box_2->IsNullPosition());
// "Hello<>"
TestPositionType after_inline_box_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_2.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(after_inline_box_2->IsNullPosition());
TestPositionType before_inline_box_2_tree =
AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_2.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_FALSE(before_inline_box_2_tree->IsNullPosition());
TestPositionType after_inline_box_2_tree = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_2.id, 0 /* child_index */);
ASSERT_FALSE(after_inline_box_2_tree->IsNullPosition());
TestPositionType before_button_empty = AXNodePosition::CreateTextPosition(
GetTreeID(), button_empty.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_FALSE(before_button_empty->IsNullPosition());
TestPositionType before_button_empty_tree =
AXNodePosition::CreateTreePosition(
GetTreeID(), button_empty.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_FALSE(before_button_empty_tree->IsNullPosition());
TestPositionType after_button_empty_tree = AXNodePosition::CreateTreePosition(
GetTreeID(), button_empty.id, 0 /* child_index */);
ASSERT_FALSE(after_button_empty_tree->IsNullPosition());
TestPositionType parent_position = before_inline_box_1->CreateParentPosition()
->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(0, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
TestPositionType leaf_position = before_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_1.id, leaf_position->anchor_id());
EXPECT_EQ(0, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
// `inline_box_1` is on a different line from `inline_box_2`, hence the
// equivalent position on the root should have an upstream affinity.
parent_position = after_inline_box_1->CreateParentPosition()
->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(AXNode::kEmbeddedCharacterLength, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, parent_position->affinity());
parent_position = after_inline_box_1_tree->CreateParentPosition()
->CreateParentPosition()
->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(1, parent_position->child_index());
leaf_position = middle_root_upstream->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_1.id, leaf_position->anchor_id());
EXPECT_EQ(9, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
parent_position =
before_inline_box_2->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(AXNode::kEmbeddedCharacterLength, parent_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
leaf_position = middle_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(inline_box_2.id, leaf_position->anchor_id());
EXPECT_EQ(0, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
parent_position =
after_inline_box_2->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
// The text offset should be after the paragraph, which is an embedded object,
// and the word "Hello".
EXPECT_EQ(6, parent_position->text_offset());
// Since the word "Hello" is on a different line from the empty button, the
// affinity at the end of the word should be upstream.
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, parent_position->affinity());
parent_position =
before_inline_box_2_tree->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(1, parent_position->child_index());
parent_position =
after_inline_box_2_tree->CreateParentPosition()->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(2, parent_position->child_index());
parent_position = before_button_empty->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTextPosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
// The empty button comes in the root's hypertext after the paragraph, which
// is an embedded object, and the word "Hello".
EXPECT_EQ(6, parent_position->text_offset());
// The empty button should start a new line, hence the affinity should be
// downstream.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, parent_position->affinity());
parent_position = before_button_empty_tree->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(2, parent_position->child_index());
parent_position = after_button_empty_tree->CreateParentPosition();
ASSERT_NE(nullptr, parent_position);
EXPECT_TRUE(parent_position->IsTreePosition());
EXPECT_EQ(root.id, parent_position->anchor_id());
EXPECT_EQ(3, parent_position->child_index());
leaf_position = after_root->AsLeafTextPosition();
ASSERT_NE(nullptr, leaf_position);
EXPECT_TRUE(leaf_position->IsTextPosition());
EXPECT_EQ(button_empty.id, leaf_position->anchor_id());
// Empty leaf objects are replaced by the embedded object character.
EXPECT_EQ(AXNode::kEmbeddedCharacterLength, leaf_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, leaf_position->affinity());
}
TEST_F(AXPositionTest, CreateNextAndPreviousLeafTextPositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreateNextLeafTextPosition) {
TestPositionType check_box_position = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, check_box_position);
TestPositionType test_position =
check_box_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// The text offset on the root points to the button since it is the first
// available leaf text position, even though it has no text content.
TestPositionType root_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, root_position);
ASSERT_TRUE(root_position->IsTextPosition());
test_position = root_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
TestPositionType button_position = AXNodePosition::CreateTextPosition(
GetTreeID(), button_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, button_position);
ASSERT_TRUE(button_position->IsTextPosition());
test_position = button_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
TestPositionType text_field_position = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 2 /* child_index */);
ASSERT_NE(nullptr, text_field_position);
test_position = text_field_position->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// The root text position should resolve to its leaf text position,
// maintaining its text_offset
TestPositionType root_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, root_position2);
ASSERT_TRUE(root_position2->IsTextPosition());
test_position = root_position2->CreateNextLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(3, test_position->text_offset());
}
TEST_F(AXPositionTest, CreatePreviousLeafTextPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position =
text_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// Create a "before text" tree position on the second line of the text box.
TestPositionType before_text_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box2_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, before_text_position);
test_position = before_text_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = test_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
TestPositionType text_field_position = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 2 /* child_index */);
ASSERT_NE(nullptr, text_field_position);
test_position = text_field_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// The text offset on the root points to the text coming from inside the check
// box.
TestPositionType check_box_position = AXNodePosition::CreateTextPosition(
GetTreeID(), check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, check_box_position);
ASSERT_TRUE(check_box_position->IsTextPosition());
test_position = check_box_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(button_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// The root text position should resolve to its leaf text position,
// maintaining its text_offset
TestPositionType root_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, root_position2);
ASSERT_TRUE(root_position2->IsTextPosition());
test_position = root_position2->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), test_position->tree_id());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(3, test_position->text_offset());
}
TEST_F(AXPositionTest, CreateNextLeafTreePosition) {
TestPositionType root_position = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 0 /* child_index */);
ASSERT_TRUE(root_position->IsTreePosition());
TestPositionType button_position = AXNodePosition::CreateTreePosition(
GetTreeID(), button_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType checkbox_position = AXNodePosition::CreateTreePosition(
GetTreeID(), check_box_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType inline_box1_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box1_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType line_break_position = AXNodePosition::CreateTreePosition(
GetTreeID(), line_break_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType inline_box2_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box2_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType test_position = root_position->CreateNextLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *button_position);
test_position = test_position->CreateNextLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *checkbox_position);
test_position = test_position->CreateNextLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *inline_box1_position);
test_position = test_position->CreateNextLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *line_break_position);
test_position = test_position->CreateNextLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *inline_box2_position);
test_position = test_position->CreateNextLeafTreePosition();
EXPECT_TRUE(test_position->IsNullPosition());
TestPositionType root_text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(root_text_position->IsTextPosition());
test_position = root_text_position->CreateNextLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *inline_box1_position);
TestPositionType inline_box1_text_position =
AXNodePosition::CreateTextPosition(GetTreeID(), inline_box1_.id,
2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(inline_box1_text_position->IsTextPosition());
test_position = inline_box1_text_position->CreateNextLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *line_break_position);
}
TEST_F(AXPositionTest, CreatePreviousLeafTreePosition) {
TestPositionType inline_box2_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box2_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_TRUE(inline_box2_position->IsTreePosition());
TestPositionType line_break_position = AXNodePosition::CreateTreePosition(
GetTreeID(), line_break_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType inline_box1_position = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box1_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType checkbox_position = AXNodePosition::CreateTreePosition(
GetTreeID(), check_box_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType button_position = AXNodePosition::CreateTreePosition(
GetTreeID(), button_.id, AXNodePosition::BEFORE_TEXT);
TestPositionType test_position =
inline_box2_position->CreatePreviousLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *line_break_position);
test_position = test_position->CreatePreviousLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *inline_box1_position);
test_position = test_position->CreatePreviousLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *checkbox_position);
test_position = test_position->CreatePreviousLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *button_position);
test_position = test_position->CreatePreviousLeafTreePosition();
EXPECT_TRUE(test_position->IsNullPosition());
TestPositionType inline_box2_text_position =
AXNodePosition::CreateTextPosition(GetTreeID(), inline_box2_.id,
2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_TRUE(inline_box2_text_position->IsTextPosition());
test_position = inline_box2_text_position->CreatePreviousLeafTreePosition();
EXPECT_TRUE(test_position->IsTreePosition());
EXPECT_EQ(*test_position, *line_break_position);
}
TEST_F(AXPositionTest,
AsLeafTextPositionBeforeAndAfterCharacterWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
ASSERT_TRUE(null_position->IsNullPosition());
TestPositionType test_position =
null_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->AsLeafTextPositionAfterCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest,
AsLeafTextPositionBeforeAndAfterCharacterAtInvalidGraphemeBoundary) {
std::vector<int> text_offsets;
SetTree(CreateMultilingualDocument(&text_offsets));
TestPositionType test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
test_position = test_position->AsLeafTextPositionAfterCharacter();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->children()[1]->id(), test_position->anchor_id());
// "text_offset_" should have been adjusted to the next grapheme boundary.
EXPECT_EQ(2, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 10 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
test_position = test_position->AsLeafTextPositionBeforeCharacter();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->children()[2]->id(), test_position->anchor_id());
// "text_offset_" should have been adjusted to the previous grapheme boundary.
EXPECT_EQ(0, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 10 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
test_position = test_position->AsLeafTextPositionBeforeCharacter();
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->children()[2]->id(), test_position->anchor_id());
// The same as above, "text_offset_" should have been adjusted to the previous
// grapheme boundary.
EXPECT_EQ(0, test_position->text_offset());
// An upstream affinity should have had no effect on the outcome and so, it
// should have been reset in order to provide consistent output from the
// method regardless of input affinity.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, AsLeafTextPositionBeforeCharacterNoAdjustment) {
// A text offset that is on the line break right after "Line 1".
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position =
text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
// A text offset that is before the line break right after "Line 1".
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
}
TEST_F(AXPositionTest, AsLeafTextPositionAfterCharacterNoAdjustment) {
// A text offset that is after "Line 2".
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position =
text_position->AsLeafTextPositionAfterCharacter();
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());
// A text offset that is before "Line 2".
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 7 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
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());
// A text offset that is on the line break right after "Line 1".
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
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(
GetTreeID(), text_field_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
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, AsLeafTextPositionBeforeCharacter) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position =
text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(3, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 13 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionBeforeCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, AsLeafTextPositionAfterCharacter) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), 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->AsLeafTextPositionAfterCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->AsLeafTextPositionAfterCharacter();
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreateNextAndPreviousCharacterPositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, AsValidPosition) {
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");
root_data.child_ids = {text_data.id};
SetTree(CreateAXTree({root_data, text_data}));
// Create a text position at MaxTextOffset.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_data.id, 9 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->IsValid());
EXPECT_EQ(9, text_position->text_offset());
// Test basic cases with static MaxTextOffset
TestPositionType test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_TRUE(test_position->IsValid());
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(text_data.id, test_position->anchor_id());
EXPECT_EQ(9, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
// AsValidPosition should not change any fields on already-valid positions.
EXPECT_TRUE(text_position->IsValid());
test_position = text_position->AsValidPosition();
EXPECT_TRUE(test_position->IsValid());
EXPECT_EQ(*test_position, *text_position);
// Now make a change to shorten MaxTextOffset. Ensure that this position is
// invalid, then call AsValidPosition and ensure that it is now valid.
text_data.SetName("some tex");
AXTreeUpdate shorten_text_update;
shorten_text_update.nodes = {text_data};
ASSERT_TRUE(GetTree()->Unserialize(shorten_text_update));
EXPECT_FALSE(text_position->IsValid());
text_position = text_position->AsValidPosition();
EXPECT_TRUE(text_position->IsValid());
EXPECT_EQ(8, text_position->text_offset());
// Now repeat the prior tests and ensure that we can create next character
// positions with the new, valid MaxTextOffset (8).
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_TRUE(test_position->IsValid());
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(text_data.id, test_position->anchor_id());
EXPECT_EQ(8, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
// AsValidPosition should create a NullPosition if a position's anchor is
// removed. This is true for both tree positions and text positions.
EXPECT_TRUE(text_position->IsValid());
TestPositionType tree_position = text_position->AsTreePosition();
ASSERT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->IsTreePosition());
EXPECT_TRUE(tree_position->IsValid());
EXPECT_EQ(0, tree_position->child_index());
AXTreeUpdate remove_node_update;
root_data.child_ids = {};
remove_node_update.nodes = {root_data};
ASSERT_TRUE(GetTree()->Unserialize(remove_node_update));
EXPECT_FALSE(text_position->IsValid());
EXPECT_FALSE(tree_position->IsValid());
text_position = text_position->AsValidPosition();
EXPECT_TRUE(text_position->IsValid());
tree_position = tree_position->AsValidPosition();
EXPECT_TRUE(tree_position->IsValid());
EXPECT_TRUE(text_position->IsNullPosition());
EXPECT_TRUE(tree_position->IsNullPosition());
}
TEST_F(AXPositionTest, AsValidPositionInDescendantOfEmptyObject) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
// ++++2 kButton
// ++++++3 kStaticText "3.14" ignored
// ++++++++4 kInlineTextBox "3.14" ignored
AXNodeData root_1;
AXNodeData button_2;
AXNodeData static_text_3;
AXNodeData inline_box_4;
root_1.id = 1;
button_2.id = 2;
static_text_3.id = 3;
inline_box_4.id = 4;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {button_2.id};
button_2.role = ax::mojom::Role::kButton;
button_2.child_ids = {static_text_3.id};
static_text_3.role = ax::mojom::Role::kStaticText;
static_text_3.SetName("3.14");
static_text_3.child_ids = {inline_box_4.id};
inline_box_4.role = ax::mojom::Role::kInlineTextBox;
inline_box_4.SetName("3.14");
SetTree(CreateAXTree({root_1, button_2, static_text_3, inline_box_4}));
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_4.id, 3, ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_TRUE(text_position->IsValid());
EXPECT_EQ(*text_position, *text_position->AsValidPosition());
TestPositionType tree_position =
AXNodePosition::CreateTreePosition(GetTreeID(), inline_box_4.id, 0);
ASSERT_NE(nullptr, tree_position);
EXPECT_TRUE(tree_position->IsTreePosition());
EXPECT_TRUE(tree_position->IsValid());
EXPECT_EQ(*tree_position, *tree_position->AsValidPosition());
static_text_3.AddState(ax::mojom::State::kIgnored);
inline_box_4.AddState(ax::mojom::State::kIgnored);
AXTreeUpdate update;
update.nodes = {static_text_3, inline_box_4};
ASSERT_TRUE(GetTree()->Unserialize(update));
EXPECT_TRUE(text_position->IsValid());
text_position = text_position->AsValidPosition();
EXPECT_TRUE(text_position->IsValid());
EXPECT_EQ(1, text_position->text_offset());
EXPECT_TRUE(tree_position->IsValid());
tree_position = tree_position->AsValidPosition();
EXPECT_TRUE(tree_position->IsValid());
EXPECT_EQ(0, tree_position->child_index());
}
TEST_F(AXPositionTest, CreateNextCharacterPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(4, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
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());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
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());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreateNextCharacterPosition(
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());
test_position = text_position->CreateNextCharacterPosition(
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 = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
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());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = text_position->CreateNextCharacterPosition(
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());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
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_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(1, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box1_.id, test_position->anchor_id());
EXPECT_EQ(1, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
EXPECT_EQ(1, test_position->text_offset());
// Affinity should have been reset to downstream.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 12 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(text_field_.id, test_position->anchor_id());
EXPECT_EQ(13, test_position->text_offset());
// Affinity should have been reset to downstream.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, CreatePreviousCharacterPosition) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType test_position =
text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(4, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(4, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(4, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(1, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(inline_box2_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(line_break_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
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());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
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());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
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());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(check_box_.id, test_position->anchor_id());
EXPECT_EQ(0, test_position->text_offset());
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
test_position = text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
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());
// Affinity should have been reset to downstream.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, CreateNextCharacterPositionAtGraphemeBoundary) {
std::vector<int> text_offsets;
SetTree(CreateMultilingualDocument(&text_offsets));
TestPositionType test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, test_position);
ASSERT_TRUE(test_position->IsTextPosition());
for (auto iter = (text_offsets.begin() + 1); iter != text_offsets.end();
++iter) {
const int text_offset = *iter;
test_position = test_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
::testing::Message message;
message << "Expecting character boundary at " << text_offset << " in\n"
<< *test_position;
SCOPED_TRACE(message);
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(text_offset, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
test_position = test_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(3, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
test_position = test_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(5, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 9 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
test_position = test_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(9, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 10 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
test_position = test_position->CreateNextCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(12, test_position->text_offset());
// Affinity should have been reset to downstream because there was a move.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, CreatePreviousCharacterPositionAtGraphemeBoundary) {
std::vector<int> text_offsets;
SetTree(CreateMultilingualDocument(&text_offsets));
TestPositionType test_position =
AXNodePosition::CreateTextPosition(GetTreeID(), GetTree()->root()->id(),
text_offsets.back() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, test_position);
ASSERT_TRUE(test_position->IsTextPosition());
for (auto iter = (text_offsets.rbegin() + 1); iter != text_offsets.rend();
++iter) {
const int text_offset = *iter;
test_position = test_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
::testing::Message message;
message << "Expecting character boundary at " << text_offset << " in\n"
<< *test_position;
SCOPED_TRACE(message);
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(text_offset, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
test_position = test_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(3, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
test_position = test_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(3, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 9 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
test_position = test_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(9, test_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kUpstream, test_position->affinity());
test_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetTree()->root()->id(), 10 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
test_position = test_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsTextPosition());
EXPECT_EQ(GetTree()->root()->id(), test_position->anchor_id());
EXPECT_EQ(9, test_position->text_offset());
// Affinity should have been reset to downstream because there was a move.
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, test_position->affinity());
}
TEST_F(AXPositionTest, ReciprocalCreateNextAndPreviousCharacterPosition) {
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 0 /* child_index */);
TestPositionType text_position = tree_position->AsTextPosition();
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
size_t next_character_moves = 0;
while (!text_position->IsNullPosition()) {
TestPositionType moved_position =
text_position->CreateNextCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
ASSERT_NE(nullptr, moved_position);
text_position = std::move(moved_position);
++next_character_moves;
}
tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, root_.child_ids.size() /* child_index */);
text_position = tree_position->AsTextPosition();
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
size_t previous_character_moves = 0;
while (!text_position->IsNullPosition()) {
TestPositionType moved_position =
text_position->CreatePreviousCharacterPosition(
AXBoundaryBehavior::CrossBoundary);
ASSERT_NE(nullptr, moved_position);
text_position = std::move(moved_position);
++previous_character_moves;
}
EXPECT_EQ(next_character_moves, previous_character_moves);
EXPECT_EQ(strlen(TEXT_VALUE), next_character_moves - 1);
}
TEST_F(AXPositionTest, CreateNextAndPreviousWordStartPositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreateNextWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, CreateNextAndPreviousWordEndPositionWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position);
TestPositionType test_position = null_position->CreateNextWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
test_position = null_position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_NE(nullptr, test_position);
EXPECT_TRUE(test_position->IsNullPosition());
}
TEST_F(AXPositionTest, OperatorEquals) {
TestPositionType null_position1 = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position1);
TestPositionType null_position2 = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position2);
EXPECT_EQ(*null_position1, *null_position2);
// Child indices must match.
TestPositionType button_position1 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 0 /* child_index */);
ASSERT_NE(nullptr, button_position1);
TestPositionType button_position2 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 0 /* child_index */);
ASSERT_NE(nullptr, button_position2);
EXPECT_EQ(*button_position1, *button_position2);
// Both child indices are invalid. It should result in equivalent null
// positions.
TestPositionType tree_position1 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 4 /* child_index */);
ASSERT_NE(nullptr, tree_position1);
TestPositionType tree_position2 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, AXNodePosition::INVALID_INDEX);
ASSERT_NE(nullptr, tree_position2);
EXPECT_EQ(*tree_position1, *tree_position2);
// An invalid position should not be equivalent to an "after children"
// position.
tree_position1 = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
3 /* child_index */);
ASSERT_NE(nullptr, tree_position1);
tree_position2 = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
-1 /* child_index */);
ASSERT_NE(nullptr, tree_position2);
EXPECT_NE(*tree_position1, *tree_position2);
// Two "after children" positions on the same node should be equivalent.
tree_position1 = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 3 /* child_index */);
ASSERT_NE(nullptr, tree_position1);
tree_position2 = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 3 /* child_index */);
ASSERT_NE(nullptr, tree_position2);
EXPECT_EQ(*tree_position1, *tree_position2);
// Two "before text" positions on the same node should be equivalent.
tree_position1 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box1_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, tree_position1);
tree_position2 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box1_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, tree_position2);
EXPECT_EQ(*tree_position1, *tree_position2);
// Both text offsets are invalid. It should result in equivalent null
// positions.
TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 15 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsNullPosition());
TestPositionType text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, -1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsNullPosition());
EXPECT_EQ(*text_position1, *text_position2);
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_EQ(*text_position1, *text_position2);
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_GT(*text_position1, *text_position2);
// Text offsets should match.
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
EXPECT_NE(*text_position1, *text_position2);
// Two "after text" positions on the same node should be equivalent.
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_EQ(*text_position1, *text_position2);
// Two "after text" positions on a parent and child should be equivalent, in
// the middle of the document...
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_EQ(*text_position1, *text_position2);
// ...and at the end of the document.
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
// Validate that we're actually at the end of the whole content by normalizing
// to the equivalent "before character" position.
EXPECT_TRUE(
text_position1->AsLeafTextPositionBeforeCharacter()->IsNullPosition());
EXPECT_TRUE(
text_position2->AsLeafTextPositionBeforeCharacter()->IsNullPosition());
// Now compare the positions.
EXPECT_EQ(*text_position1, *text_position2);
}
TEST_F(AXPositionTest, OperatorEqualsSameTextOffsetSameAnchorId) {
TestPositionType text_position_one = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position_one);
ASSERT_TRUE(text_position_one->IsTextPosition());
TestPositionType text_position_two = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position_two);
ASSERT_TRUE(text_position_two->IsTextPosition());
ASSERT_TRUE(*text_position_one == *text_position_two);
ASSERT_TRUE(*text_position_two == *text_position_one);
}
TEST_F(AXPositionTest, OperatorEqualsSameTextOffsetDifferentAnchorIdRoot) {
TestPositionType text_position_one = AXNodePosition::CreateTextPosition(
GetTreeID(), root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position_one);
ASSERT_TRUE(text_position_one->IsTextPosition());
TestPositionType text_position_two = AXNodePosition::CreateTextPosition(
GetTreeID(), check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position_two);
ASSERT_TRUE(text_position_two->IsTextPosition());
EXPECT_TRUE(*text_position_one == *text_position_two);
EXPECT_TRUE(*text_position_two == *text_position_one);
}
TEST_F(AXPositionTest, OperatorEqualsSameTextOffsetDifferentAnchorIdLeaf) {
TestPositionType text_position_one = AXNodePosition::CreateTextPosition(
GetTreeID(), button_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position_one);
ASSERT_TRUE(text_position_one->IsTextPosition());
TestPositionType text_position_two = AXNodePosition::CreateTextPosition(
GetTreeID(), check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position_two);
ASSERT_TRUE(text_position_two->IsTextPosition());
ASSERT_TRUE(*text_position_one == *text_position_two);
ASSERT_TRUE(*text_position_two == *text_position_one);
}
TEST_F(AXPositionTest, OperatorEqualsTextPositionsInTextField) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
// ++++2 kTextField editable
// ++++++3 kGenericContainer editable
// ++++++++4 kStaticText editable "Hello"
// ++++++++++5 kInlineTextBox "Hello"
AXNodeData root_1;
AXNodeData text_field_2;
AXNodeData generic_container_3;
AXNodeData static_text_4;
AXNodeData inline_box_5;
root_1.id = 1;
text_field_2.id = 2;
generic_container_3.id = 3;
static_text_4.id = 4;
inline_box_5.id = 5;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {text_field_2.id};
text_field_2.role = ax::mojom::Role::kTextField;
text_field_2.AddState(ax::mojom::State::kEditable);
text_field_2.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
text_field_2.child_ids = {generic_container_3.id};
generic_container_3.role = ax::mojom::Role::kGenericContainer;
generic_container_3.AddState(ax::mojom::State::kEditable);
generic_container_3.child_ids = {static_text_4.id};
static_text_4.role = ax::mojom::Role::kStaticText;
static_text_4.SetName("Hello");
static_text_4.child_ids = {inline_box_5.id};
inline_box_5.role = ax::mojom::Role::kInlineTextBox;
inline_box_5.SetName("Hello");
SetTree(CreateAXTree({root_1, text_field_2, generic_container_3,
static_text_4, inline_box_5}));
// TextPosition anchor_id=5 anchor_role=inlineTextBox text_offset=4
// annotated_text=hell<o>
TestPositionType inline_text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_5.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, inline_text_position);
// TextPosition anchor_id=2 anchor_role=textField text_offset=4
// annotated_text=hell<o>
TestPositionType text_field_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_2.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_field_position);
// Validate that two positions in the text field with the same text offsets
// but different anchors are logically equal.
EXPECT_EQ(*inline_text_position, *text_field_position);
EXPECT_EQ(*text_field_position, *inline_text_position);
}
TEST_F(AXPositionTest, OperatorEqualsTextPositionsInSearchBox) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
// ++++2 kSearchBox editable
// ++++++3 kGenericContainer
// ++++++++4 kGenericContainer editable
// ++++++++++5 kStaticText editable "Hello"
// ++++++++++++6 kInlineTextBox "Hello"
// ++++7 kButton
// ++++++8 kStaticText "X"
// ++++++++9 kInlineTextBox "X"
AXNodeData root_1;
AXNodeData search_box_2;
AXNodeData generic_container_3;
AXNodeData generic_container_4;
AXNodeData static_text_5;
AXNodeData inline_box_6;
AXNodeData button_7;
AXNodeData static_text_8;
AXNodeData inline_box_9;
root_1.id = 1;
search_box_2.id = 2;
generic_container_3.id = 3;
generic_container_4.id = 4;
static_text_5.id = 5;
inline_box_6.id = 6;
button_7.id = 7;
static_text_8.id = 8;
inline_box_9.id = 9;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {search_box_2.id, button_7.id};
search_box_2.role = ax::mojom::Role::kSearchBox;
search_box_2.AddState(ax::mojom::State::kEditable);
search_box_2.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
search_box_2.child_ids = {generic_container_3.id};
generic_container_3.role = ax::mojom::Role::kGenericContainer;
generic_container_3.child_ids = {generic_container_4.id};
generic_container_4.role = ax::mojom::Role::kGenericContainer;
generic_container_4.AddState(ax::mojom::State::kEditable);
generic_container_4.child_ids = {static_text_5.id};
static_text_5.role = ax::mojom::Role::kStaticText;
static_text_5.SetName("Hello");
static_text_5.child_ids = {inline_box_6.id};
inline_box_6.role = ax::mojom::Role::kInlineTextBox;
inline_box_6.SetName("Hello");
button_7.role = ax::mojom::Role::kButton;
button_7.child_ids = {static_text_8.id};
static_text_8.role = ax::mojom::Role::kStaticText;
static_text_8.SetName("X");
static_text_8.child_ids = {inline_box_9.id};
inline_box_9.role = ax::mojom::Role::kInlineTextBox;
inline_box_9.SetName("X");
SetTree(CreateAXTree({root_1, search_box_2, generic_container_3,
generic_container_4, static_text_5, inline_box_6,
button_7, static_text_8, inline_box_9}));
// TextPosition anchor_role=inlineTextBox_6 text_offset=5
// annotated_text=hello<>
TestPositionType inline_text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_6.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, inline_text_position);
// TextPosition anchor_role=search_box_2 text_offset=5 annotated_text=hello<>
TestPositionType search_box_position = AXNodePosition::CreateTextPosition(
GetTreeID(), search_box_2.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, search_box_position);
EXPECT_EQ(*search_box_position, *inline_text_position);
EXPECT_EQ(*inline_text_position, *search_box_position);
// TextPosition anchor_role=static_text_8 text_offset=0 annotated_text=<X>
TestPositionType static_text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_8.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, static_text_position);
// TextPosition anchor_role=button_7 text_offset=0 annotated_text=<X>
TestPositionType button_position = AXNodePosition::CreateTextPosition(
GetTreeID(), button_7.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, button_position);
EXPECT_EQ(*button_position, *static_text_position);
EXPECT_EQ(*static_text_position, *button_position);
}
TEST_F(AXPositionTest, OperatorsTreePositionsAroundEmbeddedCharacter) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea "<embedded_object><embedded_object>"
// ++++2 kParagraph "<embedded_object>"
// ++++++3 kLink "Hello"
// ++++++++4 kStaticText "Hello"
// ++++++++++5 kInlineTextBox "Hello"
// ++++6 kParagraph "World"
// ++++++7 kStaticText "World"
// ++++++++8 kInlineTextBox "World"
AXNodeData root_1;
AXNodeData paragraph_2;
AXNodeData link_3;
AXNodeData static_text_4;
AXNodeData inline_box_5;
AXNodeData paragraph_6;
AXNodeData static_text_7;
AXNodeData inline_box_8;
root_1.id = 1;
paragraph_2.id = 2;
link_3.id = 3;
static_text_4.id = 4;
inline_box_5.id = 5;
paragraph_6.id = 6;
static_text_7.id = 7;
inline_box_8.id = 8;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
root_1.child_ids = {paragraph_2.id, paragraph_6.id};
paragraph_2.role = ax::mojom::Role::kParagraph;
paragraph_2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph_2.child_ids = {link_3.id};
link_3.role = ax::mojom::Role::kLink;
link_3.AddState(ax::mojom::State::kLinked);
link_3.child_ids = {static_text_4.id};
static_text_4.role = ax::mojom::Role::kStaticText;
static_text_4.SetName("Hello");
static_text_4.child_ids = {inline_box_5.id};
inline_box_5.role = ax::mojom::Role::kInlineTextBox;
inline_box_5.SetName("Hello");
paragraph_6.role = ax::mojom::Role::kParagraph;
paragraph_6.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph_6.child_ids = {static_text_7.id};
static_text_7.role = ax::mojom::Role::kStaticText;
static_text_7.SetName("World");
static_text_7.child_ids = {inline_box_8.id};
inline_box_8.role = ax::mojom::Role::kInlineTextBox;
inline_box_8.SetName("World");
SetTree(
CreateAXTree({root_1, paragraph_2, link_3, static_text_4, inline_box_5,
paragraph_6, static_text_7, inline_box_8}));
TestPositionType before_root_1 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_1.id, 0 /* child_index */);
ASSERT_NE(nullptr, before_root_1);
TestPositionType middle_root_1 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_1.id, 1 /* child_index */);
ASSERT_NE(nullptr, middle_root_1);
TestPositionType after_root_1 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_1.id, 2 /* child_index */);
ASSERT_NE(nullptr, after_root_1);
TestPositionType before_paragraph_2 = AXNodePosition::CreateTreePosition(
GetTreeID(), paragraph_2.id, 0 /* child_index */);
ASSERT_NE(nullptr, before_paragraph_2);
TestPositionType after_paragraph_2 = AXNodePosition::CreateTreePosition(
GetTreeID(), paragraph_2.id, 1 /* child_index */);
ASSERT_NE(nullptr, after_paragraph_2);
TestPositionType before_paragraph_6 = AXNodePosition::CreateTreePosition(
GetTreeID(), paragraph_6.id, 0 /* child_index */);
ASSERT_NE(nullptr, before_paragraph_6);
TestPositionType after_paragraph_6 = AXNodePosition::CreateTreePosition(
GetTreeID(), paragraph_6.id, 1 /* child_index */);
ASSERT_NE(nullptr, before_paragraph_6);
TestPositionType before_inline_box_5 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_5.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, before_inline_box_5);
TestPositionType after_inline_box_5 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_5.id, 0 /* child_index */);
ASSERT_NE(nullptr, after_inline_box_5);
TestPositionType before_inline_box_8 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_8.id,
AXNodePosition::BEFORE_TEXT /* child_index */);
ASSERT_NE(nullptr, before_inline_box_8);
TestPositionType after_inline_box_8 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box_8.id, 0 /* child_index */);
ASSERT_NE(nullptr, after_inline_box_8);
EXPECT_EQ(*before_root_1, *before_paragraph_2);
EXPECT_EQ(*before_paragraph_2, *before_root_1);
EXPECT_EQ(*before_root_1, *before_inline_box_5);
EXPECT_EQ(*before_inline_box_5, *before_root_1);
EXPECT_LT(*before_root_1, *middle_root_1);
EXPECT_GT(*before_paragraph_6, *before_inline_box_5);
EXPECT_LT(*before_paragraph_2, *before_inline_box_8);
EXPECT_EQ(*middle_root_1, *before_paragraph_6);
EXPECT_EQ(*before_paragraph_6, *middle_root_1);
EXPECT_EQ(*middle_root_1, *before_inline_box_8);
EXPECT_EQ(*before_inline_box_8, *middle_root_1);
// Since tree positions do not have affinity, all of the following positions
// should be equivalent.
EXPECT_EQ(*middle_root_1, *after_paragraph_2);
EXPECT_EQ(*after_paragraph_2, *middle_root_1);
EXPECT_EQ(*middle_root_1, *after_inline_box_5);
EXPECT_EQ(*after_inline_box_5, *middle_root_1);
EXPECT_EQ(*after_root_1, *after_paragraph_6);
EXPECT_EQ(*after_paragraph_6, *after_root_1);
EXPECT_EQ(*after_root_1, *after_inline_box_8);
EXPECT_EQ(*after_inline_box_8, *after_root_1);
}
TEST_F(AXPositionTest, OperatorsTextPositionsAroundEmbeddedCharacter) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea "<embedded_object><embedded_object>"
// ++++2 kParagraph "<embedded_object>"
// ++++++3 kLink "Hello"
// ++++++++4 kStaticText "Hello"
// ++++++++++5 kInlineTextBox "Hello"
// ++++6 kParagraph "World"
// ++++++7 kStaticText "World"
// ++++++++8 kInlineTextBox "World"
AXNodeData root_1;
AXNodeData paragraph_2;
AXNodeData link_3;
AXNodeData static_text_4;
AXNodeData inline_box_5;
AXNodeData paragraph_6;
AXNodeData static_text_7;
AXNodeData inline_box_8;
root_1.id = 1;
paragraph_2.id = 2;
link_3.id = 3;
static_text_4.id = 4;
inline_box_5.id = 5;
paragraph_6.id = 6;
static_text_7.id = 7;
inline_box_8.id = 8;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
root_1.child_ids = {paragraph_2.id, paragraph_6.id};
paragraph_2.role = ax::mojom::Role::kParagraph;
paragraph_2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph_2.child_ids = {link_3.id};
link_3.role = ax::mojom::Role::kLink;
link_3.AddState(ax::mojom::State::kLinked);
link_3.child_ids = {static_text_4.id};
static_text_4.role = ax::mojom::Role::kStaticText;
static_text_4.SetName("Hello");
static_text_4.child_ids = {inline_box_5.id};
inline_box_5.role = ax::mojom::Role::kInlineTextBox;
inline_box_5.SetName("Hello");
paragraph_6.role = ax::mojom::Role::kParagraph;
paragraph_6.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
paragraph_6.child_ids = {static_text_7.id};
static_text_7.role = ax::mojom::Role::kStaticText;
static_text_7.SetName("World");
static_text_7.child_ids = {inline_box_8.id};
inline_box_8.role = ax::mojom::Role::kInlineTextBox;
inline_box_8.SetName("World");
SetTree(
CreateAXTree({root_1, paragraph_2, link_3, static_text_4, inline_box_5,
paragraph_6, static_text_7, inline_box_8}));
TestPositionType before_root_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_root_1);
TestPositionType middle_root_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_1.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, middle_root_1);
TestPositionType middle_root_1_upstream = AXNodePosition::CreateTextPosition(
GetTreeID(), root_1.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, middle_root_1_upstream);
TestPositionType after_root_1 = AXNodePosition::CreateTextPosition(
GetTreeID(), root_1.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_root_1);
TestPositionType before_paragraph_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), paragraph_2.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_paragraph_2);
// The first paragraph has a link inside it, so it will only expose a single
// "embedded object replacement character".
TestPositionType after_paragraph_2 = AXNodePosition::CreateTextPosition(
GetTreeID(), paragraph_2.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_paragraph_2);
TestPositionType before_paragraph_6 = AXNodePosition::CreateTextPosition(
GetTreeID(), paragraph_6.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_paragraph_6);
// The second paragraph contains "World".
TestPositionType after_paragraph_6 = AXNodePosition::CreateTextPosition(
GetTreeID(), paragraph_6.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_paragraph_6);
TestPositionType before_inline_box_5 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_5.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_inline_box_5);
TestPositionType middle_inline_box_5 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_5.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, middle_inline_box_5);
// "Hello".
TestPositionType after_inline_box_5 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_5.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_inline_box_5);
TestPositionType before_inline_box_8 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_8.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, before_inline_box_8);
TestPositionType middle_inline_box_8 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_8.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, middle_inline_box_8);
// "World".
TestPositionType after_inline_box_8 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_8.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, after_inline_box_8);
EXPECT_EQ(*before_root_1, *before_paragraph_2);
EXPECT_EQ(*before_paragraph_2, *before_root_1);
EXPECT_EQ(*before_root_1, *before_inline_box_5);
EXPECT_EQ(*before_inline_box_5, *before_root_1);
EXPECT_LT(*before_root_1, *middle_root_1);
EXPECT_LT(*before_paragraph_2, *before_inline_box_8);
EXPECT_EQ(*middle_root_1, *before_paragraph_6);
EXPECT_EQ(*before_paragraph_6, *middle_root_1);
EXPECT_EQ(*middle_root_1, *before_inline_box_8);
EXPECT_EQ(*before_inline_box_8, *middle_root_1);
EXPECT_GT(*middle_root_1, *after_paragraph_2);
EXPECT_LT(*after_paragraph_2, *middle_root_1);
EXPECT_GT(*middle_root_1, *after_inline_box_5);
EXPECT_EQ(*after_root_1, *middle_inline_box_8);
EXPECT_EQ(*middle_inline_box_8, *after_root_1);
// An upstream affinity on the root before the second paragraph attaches the
// position to the end of the previous line, i.e. moves it to the end of the
// first paragraph.
EXPECT_LT(*middle_root_1_upstream, *middle_root_1);
EXPECT_EQ(*middle_root_1_upstream, *after_paragraph_2);
EXPECT_EQ(*after_paragraph_2, *middle_root_1_upstream);
EXPECT_EQ(*middle_root_1_upstream, *after_inline_box_5);
EXPECT_EQ(*after_inline_box_5, *middle_root_1_upstream);
// According to the IAccessible2 Spec, a position inside an embedded object
// should be equivalent to a position right after it, if the former is not at
// the object's start.
EXPECT_EQ(*middle_root_1_upstream, *middle_inline_box_5);
EXPECT_EQ(*middle_inline_box_5, *middle_root_1_upstream);
// However, this should not apply when the two positions compared are in
// different subtrees, i.e. when they are not ancestors of one another.
EXPECT_LT(*before_inline_box_5, *middle_root_1);
EXPECT_LT(*before_inline_box_5, *before_paragraph_6);
EXPECT_LT(*before_inline_box_5, *before_inline_box_8);
EXPECT_LT(*middle_inline_box_5, *middle_root_1);
EXPECT_LT(*middle_inline_box_5, *before_paragraph_6);
EXPECT_LT(*middle_inline_box_5, *before_inline_box_8);
EXPECT_LT(*after_inline_box_5, *middle_root_1);
EXPECT_LT(*after_inline_box_5, *before_paragraph_6);
EXPECT_LT(*after_inline_box_5, *before_inline_box_8);
EXPECT_EQ(*after_root_1, *after_paragraph_6);
EXPECT_EQ(*after_paragraph_6, *after_root_1);
EXPECT_EQ(*after_root_1, *after_inline_box_8);
EXPECT_EQ(*after_inline_box_8, *after_root_1);
// Perform some of the same checks with ignored nodes, to ensure that the slow
// path (i.e. `AXPosition::SlowCompareTo`) is being used. Also, remove line
// breaking objects, to prevent affinity from being used as a distinguishing
// characteristic between positions in the two paragraphs.
root_1.AddState(ax::mojom::State::kIgnored);
root_1.RemoveBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject);
paragraph_2.AddState(ax::mojom::State::kIgnored);
paragraph_2.RemoveBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject);
inline_box_5.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box_8.id);
paragraph_6.AddState(ax::mojom::State::kIgnored);
paragraph_6.RemoveBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject);
inline_box_8.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box_5.id);
AXTreeUpdate update;
update.root_id = root_1.id;
update.nodes = {root_1, paragraph_2, inline_box_5, paragraph_6, inline_box_8};
ASSERT_TRUE(GetTree()->Unserialize(update));
EXPECT_EQ(*after_root_1, *middle_inline_box_8);
EXPECT_EQ(*middle_inline_box_8, *after_root_1);
EXPECT_LT(*before_inline_box_5, *middle_root_1);
EXPECT_LT(*before_inline_box_5, *before_paragraph_6);
EXPECT_LT(*before_inline_box_5, *before_inline_box_8);
// The absence of a line break now makes the two positions equivalent. A
// position on the root after the embedded object character representing the
// paragraph, could (according to the IAccessible2 Spec) indicate any position
// inside the paragraph, except if the position is before the paragraph.
EXPECT_EQ(*middle_inline_box_5, *middle_root_1);
EXPECT_LT(*middle_inline_box_5, *before_paragraph_6);
EXPECT_LT(*middle_inline_box_5, *before_inline_box_8);
EXPECT_EQ(*after_inline_box_5, *middle_root_1);
EXPECT_EQ(*after_inline_box_5, *before_paragraph_6);
EXPECT_EQ(*after_inline_box_5, *before_inline_box_8);
}
TEST_F(AXPositionTest, OperatorsLessThanAndGreaterThan) {
TestPositionType null_position1 = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position1);
TestPositionType null_position2 = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position2);
EXPECT_FALSE(*null_position1 < *null_position2);
EXPECT_FALSE(*null_position1 > *null_position2);
TestPositionType button_position1 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 0 /* child_index */);
ASSERT_NE(nullptr, button_position1);
TestPositionType button_position2 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 1 /* child_index */);
ASSERT_NE(nullptr, button_position2);
EXPECT_LT(*button_position1, *button_position2);
EXPECT_GT(*button_position2, *button_position1);
TestPositionType tree_position1 = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 2 /* child_index */);
ASSERT_NE(nullptr, tree_position1);
// An "after children" position.
TestPositionType tree_position2 = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 3 /* child_index */);
ASSERT_NE(nullptr, tree_position2);
EXPECT_LT(*tree_position1, *tree_position2);
EXPECT_GT(*tree_position2, *tree_position1);
// A "before text" position.
tree_position1 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box1_.id, AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, tree_position1);
// An "after text" position.
tree_position2 = AXNodePosition::CreateTreePosition(
GetTreeID(), inline_box1_.id, 0 /* child_index */);
ASSERT_NE(nullptr, tree_position2);
EXPECT_LT(*tree_position1, *tree_position2);
EXPECT_GT(*tree_position2, *tree_position1);
// Two text positions that share a common anchor.
TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
TestPositionType text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_GT(*text_position1, *text_position2);
EXPECT_LT(*text_position2, *text_position1);
// Affinities should not matter.
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_GT(*text_position1, *text_position2);
EXPECT_LT(*text_position2, *text_position1);
// An "after text" position.
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
// A "before text" position.
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_GT(*text_position1, *text_position2);
EXPECT_LT(*text_position2, *text_position1);
// A text position that is an ancestor of another.
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 5 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_GT(*text_position1, *text_position2);
EXPECT_LT(*text_position2, *text_position1);
// Two text positions that share a common ancestor.
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_GT(*text_position1, *text_position2);
EXPECT_LT(*text_position2, *text_position1);
// Two consecutive positions. One "before text" and one "after text". When
// converted to their ancestor equivalent positions in the text field, one
// will have an upstream affinity and the other a downstream affinity. This is
// because one position is right after the line break character while the
// other at the start of the line after the line break. The positions are not
// equivalent because line break characters always appear at the end of the
// line and they are part of the line they end. One way to understand why this
// makes sense is to think what should the behavior be when a line break
// character is on a blank line of its own? The line break character in that
// case forms the blank line's text contents.
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_GT(*text_position1, *text_position2);
EXPECT_LT(*text_position2, *text_position1);
// A text position at the end of the whole content versus one that isn't.
text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box2_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_TRUE(text_position1->IsTextPosition());
// Validate that we're actually at the end of the whole content by normalizing
// to the equivalent "before character" position.
EXPECT_TRUE(
text_position1->AsLeafTextPositionBeforeCharacter()->IsNullPosition());
// Now create the not-at-end-of-content position and compare.
text_position2 = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position2);
ASSERT_TRUE(text_position2->IsTextPosition());
EXPECT_GT(*text_position1, *text_position2);
EXPECT_LT(*text_position2, *text_position1);
}
TEST_F(AXPositionTest, Swap) {
TestPositionType null_position1 = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position1);
TestPositionType null_position2 = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position2);
swap(*null_position1, *null_position2);
EXPECT_TRUE(null_position1->IsNullPosition());
EXPECT_TRUE(null_position2->IsNullPosition());
TestPositionType tree_position1 = AXNodePosition::CreateTreePosition(
GetTreeID(), root_.id, 2 /* child_index */);
ASSERT_NE(nullptr, tree_position1);
TestPositionType tree_position2 = AXNodePosition::CreateTreePosition(
GetTreeID(), text_field_.id, 3 /* child_index */);
ASSERT_NE(nullptr, tree_position2);
swap(*tree_position1, *tree_position2);
EXPECT_TRUE(tree_position1->IsTreePosition());
EXPECT_EQ(GetTreeID(), tree_position1->tree_id());
EXPECT_EQ(text_field_.id, tree_position1->anchor_id());
EXPECT_EQ(3, tree_position1->child_index());
EXPECT_TRUE(tree_position1->IsTreePosition());
EXPECT_EQ(GetTreeID(), tree_position2->tree_id());
EXPECT_EQ(root_.id, tree_position2->anchor_id());
EXPECT_EQ(2, tree_position2->child_index());
swap(*tree_position1, *null_position1);
EXPECT_TRUE(tree_position1->IsNullPosition());
EXPECT_TRUE(null_position1->IsTreePosition());
EXPECT_EQ(GetTreeID(), null_position1->tree_id());
EXPECT_EQ(text_field_.id, null_position1->anchor_id());
EXPECT_EQ(3, null_position1->child_index());
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
swap(*text_position, *null_position1);
EXPECT_TRUE(null_position1->IsTextPosition());
EXPECT_EQ(GetTreeID(), text_position->tree_id());
EXPECT_EQ(line_break_.id, null_position1->anchor_id());
EXPECT_EQ(1, null_position1->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, null_position1->affinity());
EXPECT_TRUE(text_position->IsTreePosition());
EXPECT_EQ(GetTreeID(), text_position->tree_id());
EXPECT_EQ(text_field_.id, text_position->anchor_id());
EXPECT_EQ(3, text_position->child_index());
}
TEST_F(AXPositionTest, CreateNextAnchorPosition) {
// This test updates the tree structure to test a specific edge case -
// CreateNextAnchorPosition on an empty text field.
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 text_field_data;
text_field_data.id = 3;
text_field_data.role = ax::mojom::Role::kTextField;
text_field_data.AddState(ax::mojom::State::kEditable);
text_field_data.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
AXNodeData empty_text_data;
empty_text_data.id = 4;
empty_text_data.role = ax::mojom::Role::kStaticText;
empty_text_data.SetName("");
AXNodeData more_text_data;
more_text_data.id = 5;
more_text_data.role = ax::mojom::Role::kStaticText;
more_text_data.SetName("more text");
root_data.child_ids = {text_data.id, text_field_data.id, more_text_data.id};
text_field_data.child_ids = {empty_text_data.id};
SetTree(CreateAXTree({root_data, text_data, text_field_data, empty_text_data,
more_text_data}));
// Test that CreateNextAnchorPosition will successfully navigate past the
// empty text field.
TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
GetTreeID(), text_data.id, 8 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position1);
ASSERT_FALSE(text_position1->CreateNextAnchorPosition()
->CreateNextAnchorPosition()
->IsNullPosition());
}
TEST_F(AXPositionTest, CreateLinePositionsMultipleAnchorsInSingleLine) {
// This test updates the tree structure to test a specific edge case -
// Create next and previous line start/end positions on a single line composed
// by multiple anchors; only two line boundaries should be resolved: either
// the start of the "before" text or at the end of "after".
// ++1 kRootWebArea
// ++++2 kStaticText
// ++++++3 kInlineTextBox "before" kNextOnLineId=6
// ++++4 kGenericContainer
// ++++++5 kStaticText
// ++++++++6 kInlineTextBox "inside" kPreviousOnLineId=3 kNextOnLineId=8
// ++++7 kStaticText
// ++++++8 kInlineTextBox "after" kPreviousOnLineId=6
AXNodeData root;
AXNodeData inline_box1;
AXNodeData inline_box2;
AXNodeData inline_box3;
AXNodeData inline_block;
AXNodeData static_text1;
AXNodeData static_text2;
AXNodeData static_text3;
root.id = 1;
static_text1.id = 2;
inline_box1.id = 3;
inline_block.id = 4;
static_text2.id = 5;
inline_box2.id = 6;
static_text3.id = 7;
inline_box3.id = 8;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids = {static_text1.id, inline_block.id, static_text3.id};
static_text1.role = ax::mojom::Role::kStaticText;
static_text1.SetName("before");
static_text1.child_ids = {inline_box1.id};
inline_box1.role = ax::mojom::Role::kInlineTextBox;
inline_box1.SetName("before");
inline_box1.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box2.id);
inline_block.role = ax::mojom::Role::kGenericContainer;
inline_block.child_ids = {static_text2.id};
static_text2.role = ax::mojom::Role::kStaticText;
static_text2.SetName("inside");
static_text2.child_ids = {inline_box2.id};
inline_box2.role = ax::mojom::Role::kInlineTextBox;
inline_box2.SetName("inside");
inline_box2.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box1.id);
inline_box2.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
inline_box3.id);
static_text3.role = ax::mojom::Role::kStaticText;
static_text3.SetName("after");
static_text3.child_ids = {inline_box3.id};
inline_box3.role = ax::mojom::Role::kInlineTextBox;
inline_box3.SetName("after");
inline_box3.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
inline_box2.id);
SetTree(CreateAXTree({root, static_text1, inline_box1, inline_block,
static_text2, inline_box2, static_text3, inline_box3}));
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_block.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionType next_line_start_position =
text_position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, next_line_start_position);
EXPECT_TRUE(next_line_start_position->IsTextPosition());
EXPECT_EQ(inline_box3.id, next_line_start_position->anchor_id());
EXPECT_EQ(5, next_line_start_position->text_offset());
TestPositionType previous_line_start_position =
text_position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, previous_line_start_position);
EXPECT_TRUE(previous_line_start_position->IsTextPosition());
EXPECT_EQ(inline_box1.id, previous_line_start_position->anchor_id());
EXPECT_EQ(0, previous_line_start_position->text_offset());
TestPositionType next_line_end_position =
text_position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, next_line_end_position);
EXPECT_TRUE(next_line_end_position->IsTextPosition());
EXPECT_EQ(inline_box3.id, next_line_end_position->anchor_id());
EXPECT_EQ(5, next_line_end_position->text_offset());
TestPositionType previous_line_end_position =
text_position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, previous_line_end_position);
EXPECT_TRUE(previous_line_end_position->IsTextPosition());
EXPECT_EQ(inline_box1.id, previous_line_end_position->anchor_id());
EXPECT_EQ(0, previous_line_end_position->text_offset());
}
TEST_F(AXPositionTest, CreateNextWordPositionInList) {
// This test updates the tree structure to test a specific edge case -
// next word navigation inside a list with AXListMarkers nodes.
// ++1 kRootWebArea
// ++++2 kList
// ++++++3 kListItem
// ++++++++4 kListMarker
// ++++++++++5 kStaticText
// ++++++++++++6 kInlineTextBox "1. "
// ++++++++7 kStaticText
// ++++++++++8 kInlineTextBox "first item"
// ++++++9 kListItem
// ++++++++10 kListMarker
// +++++++++++11 kStaticText
// ++++++++++++++12 kInlineTextBox "2. "
// ++++++++13 kStaticText
// ++++++++++14 kInlineTextBox "second item"
AXNodeData root;
AXNodeData list;
AXNodeData list_item1;
AXNodeData list_item2;
AXNodeData list_marker1;
AXNodeData list_marker2;
AXNodeData inline_box1;
AXNodeData inline_box2;
AXNodeData inline_box3;
AXNodeData inline_box4;
AXNodeData static_text1;
AXNodeData static_text2;
AXNodeData static_text3;
AXNodeData static_text4;
root.id = 1;
list.id = 2;
list_item1.id = 3;
list_marker1.id = 4;
static_text1.id = 5;
inline_box1.id = 6;
static_text2.id = 7;
inline_box2.id = 8;
list_item2.id = 9;
list_marker2.id = 10;
static_text3.id = 11;
inline_box3.id = 12;
static_text4.id = 13;
inline_box4.id = 14;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids = {list.id};
list.role = ax::mojom::Role::kList;
list.child_ids = {list_item1.id, list_item2.id};
list_item1.role = ax::mojom::Role::kListItem;
list_item1.child_ids = {list_marker1.id, static_text2.id};
list_item1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
list_marker1.role = ax::mojom::Role::kListMarker;
list_marker1.child_ids = {static_text1.id};
static_text1.role = ax::mojom::Role::kStaticText;
static_text1.SetName("1. ");
static_text1.child_ids = {inline_box1.id};
inline_box1.role = ax::mojom::Role::kInlineTextBox;
inline_box1.SetName("1. ");
inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0});
inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{3});
static_text2.role = ax::mojom::Role::kStaticText;
static_text2.SetName("first item");
static_text2.child_ids = {inline_box2.id};
inline_box2.role = ax::mojom::Role::kInlineTextBox;
inline_box2.SetName("first item");
inline_box2.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0, 6});
inline_box2.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{5});
list_item2.role = ax::mojom::Role::kListItem;
list_item2.child_ids = {list_marker2.id, static_text4.id};
list_item2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
list_marker2.role = ax::mojom::Role::kListMarker;
list_marker2.child_ids = {static_text3.id};
static_text3.role = ax::mojom::Role::kStaticText;
static_text3.SetName("2. ");
static_text3.child_ids = {inline_box3.id};
inline_box3.role = ax::mojom::Role::kInlineTextBox;
inline_box3.SetName("2. ");
inline_box3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0});
inline_box3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{3});
static_text4.role = ax::mojom::Role::kStaticText;
static_text4.SetName("second item");
static_text4.child_ids = {inline_box4.id};
inline_box4.role = ax::mojom::Role::kInlineTextBox;
inline_box4.SetName("second item");
inline_box4.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0, 7});
inline_box4.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{6});
SetTree(CreateAXTree({root, list, list_item1, list_marker1, static_text1,
inline_box1, static_text2, inline_box2, list_item2,
list_marker2, static_text3, inline_box3, static_text4,
inline_box4}));
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box1.id, text_position->anchor_id());
ASSERT_EQ(0, text_position->text_offset());
// "1. <f>irst item\n2. second item"
text_position = text_position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box2.id, text_position->anchor_id());
ASSERT_EQ(0, text_position->text_offset());
// "1. first <i>tem\n2. second item"
text_position = text_position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box2.id, text_position->anchor_id());
ASSERT_EQ(6, text_position->text_offset());
// "1. first item\n<2>. second item"
text_position = text_position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box3.id, text_position->anchor_id());
ASSERT_EQ(0, text_position->text_offset());
// "1. first item\n2. <s>econd item"
text_position = text_position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box4.id, text_position->anchor_id());
ASSERT_EQ(0, text_position->text_offset());
// "1. first item\n2. second <i>tem"
text_position = text_position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box4.id, text_position->anchor_id());
ASSERT_EQ(7, text_position->text_offset());
}
TEST_F(AXPositionTest, CreatePreviousWordPositionInList) {
// This test updates the tree structure to test a specific edge case -
// previous word navigation inside a list with AXListMarkers nodes.
// ++1 kRootWebArea
// ++++2 kList
// ++++++3 kListItem
// ++++++++4 kListMarker
// ++++++++++5 kStaticText
// ++++++++++++6 kInlineTextBox "1. "
// ++++++++7 kStaticText
// ++++++++++8 kInlineTextBox "first item"
// ++++++9 kListItem
// ++++++++10 kListMarker
// +++++++++++11 kStaticText
// ++++++++++++++12 kInlineTextBox "2. "
// ++++++++13 kStaticText
// ++++++++++14 kInlineTextBox "second item"
AXNodeData root;
AXNodeData list;
AXNodeData list_item1;
AXNodeData list_item2;
AXNodeData list_marker1;
AXNodeData list_marker2;
AXNodeData inline_box1;
AXNodeData inline_box2;
AXNodeData inline_box3;
AXNodeData inline_box4;
AXNodeData static_text1;
AXNodeData static_text2;
AXNodeData static_text3;
AXNodeData static_text4;
root.id = 1;
list.id = 2;
list_item1.id = 3;
list_marker1.id = 4;
static_text1.id = 5;
inline_box1.id = 6;
static_text2.id = 7;
inline_box2.id = 8;
list_item2.id = 9;
list_marker2.id = 10;
static_text3.id = 11;
inline_box3.id = 12;
static_text4.id = 13;
inline_box4.id = 14;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids = {list.id};
list.role = ax::mojom::Role::kList;
list.child_ids = {list_item1.id, list_item2.id};
list_item1.role = ax::mojom::Role::kListItem;
list_item1.child_ids = {list_marker1.id, static_text2.id};
list_item1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
list_marker1.role = ax::mojom::Role::kListMarker;
list_marker1.child_ids = {static_text1.id};
static_text1.role = ax::mojom::Role::kStaticText;
static_text1.SetName("1. ");
static_text1.child_ids = {inline_box1.id};
inline_box1.role = ax::mojom::Role::kInlineTextBox;
inline_box1.SetName("1. ");
inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0});
inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{3});
static_text2.role = ax::mojom::Role::kStaticText;
static_text2.SetName("first item");
static_text2.child_ids = {inline_box2.id};
inline_box2.role = ax::mojom::Role::kInlineTextBox;
inline_box2.SetName("first item");
inline_box2.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0, 6});
inline_box2.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{5});
list_item2.role = ax::mojom::Role::kListItem;
list_item2.child_ids = {list_marker2.id, static_text4.id};
list_item2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
true);
list_marker2.role = ax::mojom::Role::kListMarker;
list_marker2.child_ids = {static_text3.id};
static_text3.role = ax::mojom::Role::kStaticText;
static_text3.SetName("2. ");
static_text3.child_ids = {inline_box3.id};
inline_box3.role = ax::mojom::Role::kInlineTextBox;
inline_box3.SetName("2. ");
inline_box3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0});
inline_box3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{3});
static_text4.role = ax::mojom::Role::kStaticText;
static_text4.SetName("second item");
static_text4.child_ids = {inline_box4.id};
inline_box4.role = ax::mojom::Role::kInlineTextBox;
inline_box4.SetName("second item");
inline_box4.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0, 7});
inline_box4.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{6});
SetTree(CreateAXTree({root, list, list_item1, list_marker1, static_text1,
inline_box1, static_text2, inline_box2, list_item2,
list_marker2, static_text3, inline_box3, static_text4,
inline_box4}));
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box4.id, 11 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box4.id, text_position->anchor_id());
ASSERT_EQ(11, text_position->text_offset());
// "1. first item\n2. second <i>tem"
text_position = text_position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box4.id, text_position->anchor_id());
ASSERT_EQ(7, text_position->text_offset());
// "1. first item\n2. <s>econd item"
text_position = text_position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box4.id, text_position->anchor_id());
ASSERT_EQ(0, text_position->text_offset());
// "1. first item\n<2>. second item"
text_position = text_position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box3.id, text_position->anchor_id());
ASSERT_EQ(0, text_position->text_offset());
// "1. first <i>tem\n2. <s>econd item"
text_position = text_position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box2.id, text_position->anchor_id());
ASSERT_EQ(6, text_position->text_offset());
// "1. <f>irst item\n2. second item"
text_position = text_position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box2.id, text_position->anchor_id());
ASSERT_EQ(0, text_position->text_offset());
// "<1>. first item\n2. second item"
text_position = text_position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(inline_box1.id, text_position->anchor_id());
ASSERT_EQ(0, text_position->text_offset());
}
TEST_F(AXPositionTest, EmptyObjectReplacedByCharacterTextNavigation) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
// ++++2 kStaticText
// ++++++3 kInlineTextBox
// ++++4 kTextField
// ++++++5 kGenericContainer ignored
// ++++6 kStaticText
// ++++++7 kInlineTextBox
// ++++8 kHeading
// ++++++9 kStaticText
// ++++++++10 kInlineTextBox
// ++++11 kGenericContainer ignored
// ++++12 kGenericContainer
// ++++13 kStaticText
// ++++14 kButton
// ++++++15 kGenericContainer ignored
// ++++++16 kGenericContainer ignored
AXNodeData root_1;
AXNodeData static_text_2;
AXNodeData inline_box_3;
AXNodeData text_field_4;
AXNodeData generic_container_5;
AXNodeData static_text_6;
AXNodeData inline_box_7;
AXNodeData heading_8;
AXNodeData static_text_9;
AXNodeData inline_box_10;
AXNodeData generic_container_11;
AXNodeData generic_container_12;
AXNodeData static_text_13;
AXNodeData button_14;
AXNodeData generic_container_15;
AXNodeData generic_container_16;
root_1.id = 1;
static_text_2.id = 2;
inline_box_3.id = 3;
text_field_4.id = 4;
generic_container_5.id = 5;
static_text_6.id = 6;
inline_box_7.id = 7;
heading_8.id = 8;
static_text_9.id = 9;
inline_box_10.id = 10;
generic_container_11.id = 11;
generic_container_12.id = 12;
static_text_13.id = 13;
button_14.id = 14;
generic_container_15.id = 15;
generic_container_16.id = 16;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {static_text_2.id, text_field_4.id,
static_text_6.id, heading_8.id,
generic_container_11.id, generic_container_12.id,
static_text_13.id, button_14.id};
static_text_2.role = ax::mojom::Role::kStaticText;
static_text_2.SetName("Hello ");
static_text_2.child_ids = {inline_box_3.id};
inline_box_3.role = ax::mojom::Role::kInlineTextBox;
inline_box_3.SetName("Hello ");
inline_box_3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{0});
inline_box_3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{6});
text_field_4.role = ax::mojom::Role::kTextField;
text_field_4.child_ids = {generic_container_5.id};
generic_container_5.role = ax::mojom::Role::kGenericContainer;
generic_container_5.AddState(ax::mojom::State::kIgnored);
static_text_6.role = ax::mojom::Role::kStaticText;
static_text_6.SetName(" world");
static_text_6.child_ids = {inline_box_7.id};
inline_box_7.role = ax::mojom::Role::kInlineTextBox;
inline_box_7.SetName(" world");
inline_box_7.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
std::vector<int32_t>{1});
inline_box_7.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
std::vector<int32_t>{6});
heading_8.role = ax::mojom::Role::kHeading;
heading_8.child_ids = {static_text_9.id};
static_text_9.role = ax::mojom::Role::kStaticText;
static_text_9.child_ids = {inline_box_10.id};
static_text_9.SetName("3.14");
inline_box_10.role = ax::mojom::Role::kInlineTextBox;
inline_box_10.SetName("3.14");
generic_container_11.role = ax::mojom::Role::kGenericContainer;
generic_container_11.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
generic_container_11.AddState(ax::mojom::State::kIgnored);
generic_container_12.role = ax::mojom::Role::kGenericContainer;
generic_container_12.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
static_text_13.role = ax::mojom::Role::kStaticText;
static_text_13.SetName("hey");
button_14.role = ax::mojom::Role::kButton;
button_14.child_ids = {generic_container_15.id, generic_container_16.id};
generic_container_15.role = ax::mojom::Role::kGenericContainer;
generic_container_15.AddState(ax::mojom::State::kIgnored);
generic_container_16.role = ax::mojom::Role::kGenericContainer;
generic_container_16.AddState(ax::mojom::State::kIgnored);
SetTree(CreateAXTree(
{root_1, static_text_2, inline_box_3, text_field_4, generic_container_5,
static_text_6, inline_box_7, heading_8, static_text_9, inline_box_10,
generic_container_11, generic_container_12, static_text_13, button_14,
generic_container_15, generic_container_16}));
// CreateNextWordStartPosition tests.
TestPositionType position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_3.id, 0 /* child_index_or_text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionType result_position =
position->CreateNextWordStartPosition(AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
EXPECT_EQ(text_field_4.id, result_position->anchor_id());
EXPECT_EQ(0, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(AXNode::kEmbeddedCharacter, result_position->GetText());
position = std::move(result_position);
result_position =
position->CreateNextWordStartPosition(AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
EXPECT_EQ(inline_box_7.id, result_position->anchor_id());
EXPECT_EQ(1, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(u" world", result_position->GetText());
// CreatePreviousWordStartPosition tests.
position = std::move(result_position);
result_position = position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
EXPECT_EQ(text_field_4.id, result_position->anchor_id());
EXPECT_EQ(0, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(AXNode::kEmbeddedCharacter, result_position->GetText());
position = std::move(result_position);
result_position = position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
EXPECT_EQ(inline_box_3.id, result_position->anchor_id());
EXPECT_EQ(0, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(u"Hello ", result_position->GetText());
// CreateNextWordEndPosition tests.
position = std::move(result_position);
result_position =
position->CreateNextWordEndPosition(AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
EXPECT_EQ(inline_box_3.id, result_position->anchor_id());
EXPECT_EQ(6, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(u"Hello ", result_position->GetText());
position = std::move(result_position);
result_position =
position->CreateNextWordEndPosition(AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
// The position would be on `text_field_4` instead of on `generic_container_5`
// because the latter is ignored, and by design we prefer not to create
// positions on ignored nodes if it could be avoided.
EXPECT_EQ(text_field_4.id, result_position->anchor_id());
EXPECT_EQ(1, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(AXNode::kEmbeddedCharacter, result_position->GetText());
position = std::move(result_position);
result_position =
position->CreateNextWordEndPosition(AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
EXPECT_EQ(inline_box_7.id, result_position->anchor_id());
EXPECT_EQ(6, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(u" world", result_position->GetText());
// CreatePreviousWordEndPosition tests.
position = std::move(result_position);
result_position = position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
// The position would be on `text_field_4` instead of on `generic_container_5`
// because the latter is ignored, and by design we prefer not to create
// positions on ignored nodes if it could be avoided.
EXPECT_EQ(text_field_4.id, result_position->anchor_id());
EXPECT_EQ(1, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(AXNode::kEmbeddedCharacter, result_position->GetText());
position = std::move(result_position);
result_position = position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
EXPECT_TRUE(result_position->IsTextPosition());
EXPECT_EQ(inline_box_3.id, result_position->anchor_id());
EXPECT_EQ(6, result_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, result_position->affinity());
EXPECT_EQ(u"Hello ", result_position->GetText());
// Positions on descendants of empty objects that have been replaced by the
// "embedded object replacement character" are valid, to allow for navigating
// inside of text controls.
position = AXNodePosition::CreateTextPosition(
GetTreeID(), generic_container_5.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_FALSE(position->IsNullPosition());
EXPECT_TRUE(position->GetText().empty());
// `AXPosition::GetText()` on a node that is the parent of a set of text nodes
// and a non-text node, the latter represented by an embedded object
// replacement character.
position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
// Hello <embedded> world<embedded><embedded>hey<embedded><embedded>
std::u16string expected_text =
base::StrCat({u"Hello ", AXNode::kEmbeddedCharacter, u" world",
AXNode::kEmbeddedCharacter, AXNode::kEmbeddedCharacter,
u"hey", AXNode::kEmbeddedCharacter});
EXPECT_EQ(expected_text, position->GetText());
// A position on an empty object that has been replaced by an "embedded object
// replacement character".
position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_4.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_EQ(AXNode::kEmbeddedCharacterLength, position->MaxTextOffset())
<< *position;
position = position->CreateParentPosition();
// Hello <embedded> world<embedded><embedded>hey<embedded>
EXPECT_EQ(19, position->MaxTextOffset()) << *position;
// `AXPosition::MaxTextOffset()` on a node which is the parent of a set of
// text nodes and non-text nodes, the latter represented by "embedded object
// replacement characters".
//
// Hello <embedded> world<embedded><embedded>hey<embedded>
position = AXNodePosition::CreateTextPosition(
GetTreeID(), root_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
EXPECT_EQ(19, position->MaxTextOffset()) << *position;
// The following is to test a specific edge case with heading navigation,
// occurring in `AXPosition::CreatePreviousFormatStartPosition`.
//
// When the position is at the beginning of an unignored empty object,
// preceded by an ignored empty object, which is itself preceded by a heading
// node, the previous format start position should stay on this unignored
// empty object. It shouldn't move to the beginning of the heading.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), generic_container_12.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
text_position = text_position->CreatePreviousFormatStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(generic_container_12.id, text_position->anchor_id());
EXPECT_EQ(0, text_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, text_position->affinity());
// The following is to test a specific edge case that occurs when all the
// children of a node are ignored and that node could be considered as an
// empty object, which would be replaced by an embedded object replacement
// character, (e.g., a button).
//
// The button element should be treated as a leaf node even though it has a
// child. Because its only child is ignored, the button should be considered
// as an empty object replaced by character and we should be able to create a
// leaf position in the button node.
text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text_13.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, text_position);
text_position = text_position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsLeafTextPosition());
EXPECT_EQ(button_14.id, text_position->anchor_id());
EXPECT_EQ(1, text_position->text_offset());
EXPECT_EQ(ax::mojom::TextAffinity::kDownstream, text_position->affinity());
// We shouldn't infinitely loop when trying to get the previous position
// from a descendant of embedded object character.
TestPositionType generic_container_position =
AXNodePosition::CreateTreePosition(GetTreeID(), generic_container_16.id,
AXNodePosition::BEFORE_TEXT);
ASSERT_NE(nullptr, generic_container_position);
ASSERT_TRUE(generic_container_position->IsTreePosition());
EXPECT_EQ(generic_container_16.id, generic_container_position->anchor_id());
text_position = generic_container_position->CreatePreviousLeafTextPosition();
EXPECT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(GetTreeID(), text_position->tree_id());
EXPECT_EQ(button_14.id, text_position->anchor_id());
}
TEST_F(AXPositionTest, EmptyObjectReplacedByCharacterEmbedObject) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// Parent Tree
// ++1 kRootWebArea
// ++++2 kEmbeddedObject
//
// Child Tree
// ++1 kDocument
ui::AXTreeID child_tree_id = ui::AXTreeID::CreateNewAXTreeID();
// Create tree manager for parent tree.
AXNodeData root;
AXNodeData embed_object;
root.id = 1;
embed_object.id = 2;
root.role = ax::mojom::Role::kRootWebArea;
root.child_ids = {embed_object.id};
embed_object.role = ax::mojom::Role::kEmbeddedObject;
embed_object.AddChildTreeId(child_tree_id);
SetTree(CreateAXTree({root, embed_object}));
// Create tree manager for child tree.
AXNodeData child_root;
child_root.id = 1;
child_root.role = ax::mojom::Role::kPdfRoot;
AXTreeUpdate update;
update.tree_data.tree_id = child_tree_id;
update.tree_data.parent_tree_id = GetTreeID();
update.has_tree_data = true;
update.root_id = child_root.id;
update.nodes.push_back(child_root);
TestAXTreeManager child_tree_manager(std::make_unique<AXTree>(update));
// Verify that kEmbeddedObject node with child tree is not treated as an
// empty object.
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), embed_object.id, 0 /* child_index */);
ASSERT_TRUE(tree_position->IsTreePosition());
EXPECT_FALSE(tree_position->IsLeaf());
}
TEST_F(AXPositionTest, TextNavigationWithCollapsedCombobox) {
// On Windows, a <select> element is replaced by a combobox that contains
// an AXMenuListPopup parent of AXMenuListOptions. When the select dropdown is
// collapsed, the subtree of that combobox needs to be hidden and, when
// expanded, it must be accessible in the tree. This test ensures we can't
// navigate into the options of a collapsed menu list popup.
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
// ++++2 kStaticText "Hi"
// ++++++3 kInlineTextBox "Hi"
// ++++4 kPopUpButton
// ++++++5 kMenuListPopup
// ++++++++6 kMenuListOption "Option"
// ++++7 kStaticText "3.14"
// ++++++8 kInlineTextBox "3.14"
AXNodeData root_1;
AXNodeData static_text_2;
AXNodeData inline_box_3;
AXNodeData popup_button_4;
AXNodeData menu_list_popup_5;
AXNodeData menu_list_option_6;
AXNodeData static_text_7;
AXNodeData inline_box_8;
root_1.id = 1;
static_text_2.id = 2;
inline_box_3.id = 3;
popup_button_4.id = 4;
menu_list_popup_5.id = 5;
menu_list_option_6.id = 6;
static_text_7.id = 7;
inline_box_8.id = 8;
root_1.role = ax::mojom::Role::kRootWebArea;
root_1.child_ids = {static_text_2.id, popup_button_4.id, static_text_7.id};
static_text_2.role = ax::mojom::Role::kStaticText;
static_text_2.SetName("Hi");
static_text_2.child_ids = {inline_box_3.id};
inline_box_3.role = ax::mojom::Role::kInlineTextBox;
inline_box_3.SetName("Hi");
inline_box_3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
{0});
inline_box_3.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds, {2});
popup_button_4.role = ax::mojom::Role::kPopUpButton;
popup_button_4.child_ids = {menu_list_popup_5.id};
popup_button_4.AddState(ax::mojom::State::kCollapsed);
menu_list_popup_5.role = ax::mojom::Role::kMenuListPopup;
menu_list_popup_5.child_ids = {menu_list_option_6.id};
menu_list_option_6.role = ax::mojom::Role::kMenuListOption;
menu_list_option_6.SetName("Option");
menu_list_option_6.SetNameFrom(ax::mojom::NameFrom::kContents);
static_text_7.role = ax::mojom::Role::kStaticText;
static_text_7.SetName("3.14");
static_text_7.child_ids = {inline_box_8.id};
inline_box_8.role = ax::mojom::Role::kInlineTextBox;
inline_box_8.SetName("3.14");
inline_box_8.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
{0});
inline_box_8.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds, {4});
SetTree(CreateAXTree({root_1, static_text_2, inline_box_3, popup_button_4,
menu_list_popup_5, menu_list_option_6, static_text_7,
inline_box_8}));
// Collapsed - Forward navigation.
TestPositionType position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_3.id, 0, ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, position);
position = position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, position);
EXPECT_EQ(popup_button_4.id, position->anchor_id());
EXPECT_EQ(0, position->text_offset());
position = position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, position);
EXPECT_EQ(inline_box_8.id, position->anchor_id());
EXPECT_EQ(0, position->text_offset());
// Collapsed - Backward navigation.
position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_8.id, 4, ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, position);
position = position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, position);
EXPECT_EQ(popup_button_4.id, position->anchor_id());
// The content of this popup button should be replaced with the empty object
// character of length 1.
EXPECT_EQ(1, position->text_offset());
position = position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, position);
EXPECT_EQ(inline_box_3.id, position->anchor_id());
EXPECT_EQ(2, position->text_offset());
// Expand the combobox for the rest of the test.
popup_button_4.RemoveState(ax::mojom::State::kCollapsed);
popup_button_4.AddState(ax::mojom::State::kExpanded);
AXTreeUpdate update;
update.nodes = {popup_button_4};
ASSERT_TRUE(GetTree()->Unserialize(update));
// Expanded - Forward navigation.
position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_3.id, 0, ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, position);
position = position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, position);
EXPECT_EQ(menu_list_option_6.id, position->anchor_id());
EXPECT_EQ(0, position->text_offset());
position = position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, position);
EXPECT_EQ(inline_box_8.id, position->anchor_id());
EXPECT_EQ(0, position->text_offset());
// Expanded- Backward navigation.
position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box_8.id, 4, ax::mojom::TextAffinity::kDownstream);
ASSERT_NE(nullptr, position);
position = position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, position);
EXPECT_EQ(menu_list_option_6.id, position->anchor_id());
EXPECT_EQ(1, position->text_offset());
position = position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
ASSERT_NE(nullptr, position);
EXPECT_EQ(inline_box_3.id, position->anchor_id());
EXPECT_EQ(2, position->text_offset());
}
TEST_F(AXPositionTest, GetUnignoredSelectionWithLeafNodes) {
testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
AXNodeData parent_data;
parent_data.id = 2;
parent_data.role = ax::mojom::Role::kGenericContainer;
AXNodeData child_1_data;
child_1_data.id = 3;
child_1_data.role = ax::mojom::Role::kGenericContainer;
child_1_data.AddState(ax::mojom::State::kIgnored);
AXNodeData child_2_data;
child_2_data.id = 4;
child_2_data.role = ax::mojom::Role::kGenericContainer;
child_2_data.AddState(ax::mojom::State::kIgnored);
root_data.child_ids = {parent_data.id};
parent_data.child_ids = {child_1_data.id, child_2_data.id};
AXTreeData data;
data.tree_id = AXTreeID::CreateNewAXTreeID();
data.parent_tree_id = AXTreeID();
data.sel_anchor_object_id = child_1_data.id;
data.sel_anchor_offset = 0;
data.sel_focus_object_id = child_1_data.id;
data.sel_focus_offset = 0;
AXTreeUpdate update;
update.tree_data = data;
update.has_tree_data = true;
update.root_id = root_data.id;
update.nodes = {root_data, parent_data, child_1_data, child_2_data};
SetTree(std::make_unique<AXTree>(update));
AXTree* tree = GetTree();
TestPositionType parent_at_0 =
AXNodePosition::CreateTreePosition(GetTreeID(), parent_data.id, 0);
TestPositionType parent_at_1 =
AXNodePosition::CreateTreePosition(GetTreeID(), parent_data.id, 1);
TestPositionType parent_at_2 =
AXNodePosition::CreateTreePosition(GetTreeID(), parent_data.id, 2);
TestPositionType child_1_at_0 =
AXNodePosition::CreateTreePosition(GetTreeID(), child_1_data.id, 0);
TestPositionType child_2_at_0 =
AXNodePosition::CreateTreePosition(GetTreeID(), child_2_data.id, 0);
EXPECT_EQ(*parent_at_0, *parent_at_0->AsValidPosition());
EXPECT_EQ(*parent_at_1, *parent_at_1->AsValidPosition());
EXPECT_EQ(*parent_at_2, *parent_at_2->AsValidPosition());
EXPECT_EQ(*parent_at_0, *child_1_at_0->AsValidPosition());
EXPECT_EQ(*parent_at_0, *child_2_at_0->AsValidPosition());
for (TestPositionType::pointer position :
{parent_at_0.get(), child_1_at_0.get(), child_2_at_0.get()}) {
AXNodePosition::AXPositionInstance valid = position->AsValidPosition();
EXPECT_TRUE(position->IsLeaf());
EXPECT_TRUE(valid->IsLeaf());
data.sel_anchor_object_id = position->anchor_id();
data.sel_anchor_offset = position->child_index();
data.sel_focus_object_id = position->anchor_id();
data.sel_focus_offset = position->child_index();
tree->UpdateDataForTesting(data);
// Should not crash.
AXNode::OwnerTree::Selection s = tree->GetUnignoredSelection();
EXPECT_EQ(valid->anchor_id(), s.anchor_object_id);
EXPECT_EQ(valid->child_index(), s.anchor_offset);
EXPECT_EQ(valid->anchor_id(), s.focus_object_id);
EXPECT_EQ(valid->child_index(), s.focus_offset);
}
}
//
// Parameterized tests.
//
TEST_P(AXPositionExpandToEnclosingTextBoundaryTestWithParam,
TextPositionBeforeLine2) {
// Create a text position right before "Line 2". This should be at the start
// of many text boundaries, e.g. line, paragraph and word.
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), text_field_.id, 7 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position->IsTextPosition());
TestPositionRange range = text_position->ExpandToEnclosingTextBoundary(
GetParam().boundary, GetParam().expand_behavior);
EXPECT_EQ(GetParam().expected_anchor_position, range.anchor()->ToString());
EXPECT_EQ(GetParam().expected_focus_position, range.focus()->ToString());
}
TEST_P(AXPositionCreatePositionAtTextBoundaryTestWithParam,
TextPositionBeforeStaticText) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), static_text2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position->IsTextPosition());
text_position = text_position->CreatePositionAtTextBoundary(
GetParam().boundary, GetParam().direction, GetParam().boundary_behavior);
EXPECT_NE(nullptr, text_position);
EXPECT_EQ(GetParam().expected_text_position, text_position->ToString());
}
TEST_P(AXPositionTextNavigationTestWithParam,
TraverseTreeStartingWithAffinityDownstream) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetParam().start_node_id, GetParam().start_offset,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(text_position->IsTextPosition());
for (const std::string& expectation : GetParam().expectations) {
text_position = GetParam().TestMethod.Run(text_position);
EXPECT_NE(nullptr, text_position);
EXPECT_EQ(expectation, text_position->ToString());
}
}
TEST_P(AXPositionTextNavigationTestWithParam,
TraverseTreeStartingWithAffinityUpstream) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), GetParam().start_node_id, GetParam().start_offset,
ax::mojom::TextAffinity::kUpstream);
ASSERT_TRUE(text_position->IsTextPosition());
for (const std::string& expectation : GetParam().expectations) {
text_position = GetParam().TestMethod.Run(text_position);
EXPECT_NE(nullptr, text_position);
EXPECT_EQ(expectation, text_position->ToString());
}
}
//
// Instantiations of parameterized tests.
//
INSTANTIATE_TEST_SUITE_P(
ExpandToEnclosingTextBoundary,
AXPositionExpandToEnclosingTextBoundaryTestWithParam,
::testing::Values(
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kCharacter,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=6 affinity=downstream "
"annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kCharacter,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=8 affinity=downstream "
"annotated_text=Line 1\nL<i>ne 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kFormat, AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kFormat,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineEnd,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=6 affinity=downstream "
"annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineEnd,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=6 affinity=downstream "
"annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineStart,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineStart,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineStartOrEnd,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=6 affinity=downstream "
"annotated_text=Line 1<\n>Line 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineStartOrEnd,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kObject, AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kObject,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphEnd,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=7 affinity=upstream "
"annotated_text=Line 1\n<L>ine 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphEnd,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=7 affinity=upstream "
"annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphStart,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphStart,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphStartOrEnd,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=7 affinity=upstream "
"annotated_text=Line 1\n<L>ine 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphStartOrEnd,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 affinity=downstream "
"annotated_text=Line 1\nLine 2<>"},
// TODO(accessibility): Add tests for sentence boundary.
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kWebPage,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=1 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=9 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kWebPage,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=1 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=9 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordEnd,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=6 affinity=downstream "
"annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=11 affinity=downstream "
"annotated_text=Line 1\nLine< >2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordEnd,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=6 affinity=downstream "
"annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=11 affinity=downstream "
"annotated_text=Line 1\nLine< >2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordStart,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=5 affinity=downstream "
"annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordStart,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=12 affinity=downstream "
"annotated_text=Line 1\nLine <2>"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordStartOrEnd,
AXRangeExpandBehavior::kLeftFirst,
"TextPosition anchor_id=4 text_offset=5 affinity=downstream "
"annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=4 text_offset=6 affinity=downstream "
"annotated_text=Line 1<\n>Line 2"},
ExpandToEnclosingTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordStartOrEnd,
AXRangeExpandBehavior::kRightFirst,
"TextPosition anchor_id=4 text_offset=7 affinity=downstream "
"annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=11 affinity=downstream "
"annotated_text=Line 1\nLine< >2"}));
// Only test with AXBoundaryBehavior::CrossBoundary for now.
// TODO(accessibility): Add more tests for other boundary behaviors if needed.
INSTANTIATE_TEST_SUITE_P(
CreatePositionAtTextBoundary,
AXPositionCreatePositionAtTextBoundaryTestWithParam,
::testing::Values(
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kCharacter,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=7 text_offset=0 affinity=downstream "
"annotated_text=<\n>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kCharacter,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=1 affinity=downstream "
"annotated_text=L<i>ne 2"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kFormat,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=7 text_offset=0 affinity=downstream "
"annotated_text=<\n>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kFormat,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineEnd,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=7 text_offset=0 affinity=downstream "
"annotated_text=<\n>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineEnd,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineStart,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=6 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineStart,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary, "NullPosition"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineStartOrEnd,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=6 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kLineStartOrEnd,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kObject,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 2"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kObject,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphEnd,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=3 text_offset=0 affinity=downstream "
"annotated_text=<>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphEnd,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphStart,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=6 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphStart,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary, "NullPosition"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphStartOrEnd,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=6 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kParagraphStartOrEnd,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
// TODO(accessibility): Add tests for sentence boundary.
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kWebPage,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=1 text_offset=0 affinity=downstream "
"annotated_text=<L>ine 1\nLine 2"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kWebPage,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=9 text_offset=6 affinity=downstream "
"annotated_text=Line 2<>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordEnd,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=6 text_offset=6 affinity=downstream "
"annotated_text=Line 1<>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordEnd,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=4 affinity=downstream "
"annotated_text=Line< >2"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordStart,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=6 text_offset=5 affinity=downstream "
"annotated_text=Line <1>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordStart,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=5 affinity=downstream "
"annotated_text=Line <2>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordStartOrEnd,
ax::mojom::MoveDirection::kBackward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=6 text_offset=5 affinity=downstream "
"annotated_text=Line <1>"},
CreatePositionAtTextBoundaryTestParam{
ax::mojom::TextBoundary::kWordStartOrEnd,
ax::mojom::MoveDirection::kForward,
AXBoundaryBehavior::CrossBoundary,
"TextPosition anchor_id=8 text_offset=4 affinity=downstream "
"annotated_text=Line< >2"}));
INSTANTIATE_TEST_SUITE_P(
CreateNextWordStartPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=5 "
"affinity=downstream annotated_text=Line <1>",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=5 "
"affinity=downstream annotated_text=Line <2>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=5 "
"affinity=downstream annotated_text=Line <2>",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextWordStartPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=5 "
"affinity=downstream annotated_text=Line <1>",
"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=5 "
"affinity=downstream annotated_text=Line <2>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextWordStartPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=5 "
"affinity=downstream annotated_text=Line <1>",
"TextPosition anchor_id=5 text_offset=5 "
"affinity=downstream annotated_text=Line <1>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=5 "
"affinity=downstream annotated_text=Line <2>",
"TextPosition anchor_id=9 text_offset=5 "
"affinity=downstream annotated_text=Line <2>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextWordStartPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=5 "
"affinity=downstream annotated_text=Line <1>",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=5 "
"affinity=downstream annotated_text=Line <2>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=5 "
"affinity=downstream annotated_text=Line <2>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousWordStartPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=6 text_offset=5 "
"affinity=downstream annotated_text=Line <1>",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousWordStartPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousWordStartPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=1 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=4 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=5 "
"affinity=downstream annotated_text=Line <1>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousWordStartPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=12 "
"affinity=downstream annotated_text=Line 1\nLine <2>",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=5 "
"affinity=downstream annotated_text=Line <1>\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=6 text_offset=5 "
"affinity=downstream annotated_text=Line <1>",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextWordEndPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=9 text_offset=4 "
"affinity=downstream annotated_text=Line< >2",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextWordEndPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextWordEndPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=1 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=4 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=4 "
"affinity=downstream annotated_text=Line< >2"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextWordEndPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=9 text_offset=4 "
"affinity=downstream annotated_text=Line< >2",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextWordEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousWordEndPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=6 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=6 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousWordEndPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{
"TextPosition anchor_id=1 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousWordEndPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=4 "
"affinity=downstream annotated_text=Line< >2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousWordEndPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=11 "
"affinity=downstream annotated_text=Line 1\nLine< >2",
"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=4 "
"affinity=downstream annotated_text=Line< >1\nLine 2",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousWordEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=6 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=6 text_offset=4 "
"affinity=downstream annotated_text=Line< >1",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextLineStartPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextLineStartPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextLineStartPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextLineStartPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousLineStartPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
13 /* text_offset at the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousLineStartPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousLineStartPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
13 /* text_offset at the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousLineStartPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextLineEndPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextLineEndPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextLineEndPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextLineEndPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousLineEndPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
5 /* text_offset on the last character of "Line 1". */,
{"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
5 /* text_offset on the last character of "Line 1". */,
{"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=6 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
0 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=0 "
"affinity=downstream annotated_text=<\n>",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousLineEndPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
0 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousLineEndPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
12 /* text_offset one before the end of root. */,
{"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
12 /* text_offset one before the end of text field */,
{"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX1_ID,
2 /* text_offset */,
{"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=6 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=6 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
0 /* text_offset */,
{"TextPosition anchor_id=6 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=6 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousLineEndPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=6 "
"affinity=downstream annotated_text=Line 1<\n>Line 2",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=6 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousLineEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
0 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=0 "
"affinity=downstream annotated_text=<\n>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=2 text_offset=0 "
"affinity=downstream annotated_text=<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextParagraphStartPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextParagraphStartPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextParagraphStartPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextParagraphStartPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousParagraphStartPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
13 /* text_offset at the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousParagraphStartPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousParagraphStartPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
13 /* text_offset at the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousParagraphStartPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=downstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
5 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=5 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphStartPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1",
"TextPosition anchor_id=6 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextParagraphEndPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextParagraphEndPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>",
"TextPosition anchor_id=5 text_offset=6 "
"affinity=downstream annotated_text=Line 1<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextParagraphEndPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
LINE_BREAK_ID,
0 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
LINE_BREAK_ID,
1 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreateNextParagraphEndPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
0 /* text_offset */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=1 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
0 /* text_offset */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>",
"TextPosition anchor_id=4 text_offset=13 "
"affinity=downstream annotated_text=Line 1\nLine 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
STATIC_TEXT1_ID,
1 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreateNextParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>",
"TextPosition anchor_id=9 text_offset=6 "
"affinity=downstream annotated_text=Line 2<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousParagraphEndPositionWithBoundaryBehaviorCrossBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
ROOT_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
TEXT_FIELD_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"NullPosition"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::CrossBoundary);
}),
INLINE_BOX2_ID,
0 /* text_offset */,
{"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"NullPosition"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousParagraphEndPositionWithBoundaryBehaviorStopAtAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
ROOT_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
TEXT_FIELD_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=4 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtAnchorBoundary);
}),
INLINE_BOX2_ID,
0 /* text_offset */,
{"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2",
"TextPosition anchor_id=9 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 2"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousParagraphEndPositionWithBoundaryBehaviorStopIfAlreadyAtBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
ROOT_ID,
12 /* text_offset one before the end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
TEXT_FIELD_ID,
12 /* text_offset one before the end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX1_ID,
2 /* text_offset */,
{"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
INLINE_BOX2_ID,
0 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
LINE_BREAK_ID,
0 /* text_offset */,
{"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
}),
LINE_BREAK_ID,
1 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>"}}));
INSTANTIATE_TEST_SUITE_P(
CreatePreviousParagraphEndPositionWithBoundaryBehaviorStopAtLastAnchorBoundary,
AXPositionTextNavigationTestWithParam,
::testing::Values(
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
13 /* text_offset at end of root. */,
{"TextPosition anchor_id=1 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
13 /* text_offset at end of text field */,
{"TextPosition anchor_id=4 text_offset=7 "
"affinity=upstream annotated_text=Line 1\n<L>ine 2",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
ROOT_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2",
"TextPosition anchor_id=1 text_offset=0 "
"affinity=downstream annotated_text=<L>ine 1\nLine 2"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
TEXT_FIELD_ID,
5 /* text_offset on the last character of "Line 1". */,
{"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
4 /* text_offset */,
{"TextPosition anchor_id=7 text_offset=1 "
"affinity=downstream annotated_text=\n<>",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>"}},
TextNavigationTestParam{
base::BindRepeating([](const TestPositionType& position) {
return position->CreatePreviousParagraphEndPosition(
AXBoundaryBehavior::StopAtLastAnchorBoundary);
}),
INLINE_BOX2_ID,
0 /* text_offset */,
{"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>",
"TextPosition anchor_id=3 text_offset=0 "
"affinity=downstream annotated_text=<>"}}));
} // namespace ui