// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/accessibility/ax_range.h"

#include <memory>
#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_node_position.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/accessibility/single_ax_tree_manager.h"
#include "ui/accessibility/test_ax_node_helper.h"

namespace ui {

using TestPositionInstance =
    std::unique_ptr<AXPosition<AXNodePosition, AXNode>>;
using TestPositionRange = AXRange<AXPosition<AXNodePosition, AXNode>>;

namespace {

constexpr AXNodeID ROOT_ID = 1;
constexpr AXNodeID DIV1_ID = 2;
constexpr AXNodeID BUTTON_ID = 3;
constexpr AXNodeID DIV2_ID = 4;
constexpr AXNodeID CHECK_BOX1_ID = 5;
constexpr AXNodeID CHECK_BOX2_ID = 6;
constexpr AXNodeID TEXT_FIELD_ID = 7;
constexpr AXNodeID STATIC_TEXT1_ID = 8;
constexpr AXNodeID INLINE_BOX1_ID = 9;
constexpr AXNodeID LINE_BREAK1_ID = 10;
constexpr AXNodeID INLINE_BOX_LINE_BREAK1_ID = 11;
constexpr AXNodeID STATIC_TEXT2_ID = 12;
constexpr AXNodeID INLINE_BOX2_ID = 13;
constexpr AXNodeID LINE_BREAK2_ID = 14;
constexpr AXNodeID INLINE_BOX_LINE_BREAK2_ID = 15;
constexpr AXNodeID PARAGRAPH_ID = 16;
constexpr AXNodeID STATIC_TEXT3_ID = 17;
constexpr AXNodeID INLINE_BOX3_ID = 18;
constexpr AXNodeID EMPTY_PARAGRAPH_ID = 19;

class TestAXRangeScreenRectDelegate : public AXRangeRectDelegate {
 public:
  explicit TestAXRangeScreenRectDelegate(SingleAXTreeManager* tree_manager)
      : tree_manager_(tree_manager) {}
  virtual ~TestAXRangeScreenRectDelegate() = default;
  TestAXRangeScreenRectDelegate(const TestAXRangeScreenRectDelegate& delegate) =
      delete;
  TestAXRangeScreenRectDelegate& operator=(
      const TestAXRangeScreenRectDelegate& delegate) = delete;

  gfx::Rect GetInnerTextRangeBoundsRect(
      AXTreeID tree_id,
      AXNodeID node_id,
      int start_offset,
      int end_offset,
      const ui::AXClippingBehavior clipping_behavior,
      AXOffscreenResult* offscreen_result) override {
    if (tree_manager_->GetTreeID() != tree_id)
      return gfx::Rect();

    AXNode* node = tree_manager_->GetNode(node_id);
    if (!node)
      return gfx::Rect();

    TestAXNodeHelper* wrapper =
        TestAXNodeHelper::GetOrCreate(tree_manager_->GetTree(), node);
    return wrapper->GetInnerTextRangeBoundsRect(
        start_offset, end_offset, AXCoordinateSystem::kScreenDIPs,
        clipping_behavior, offscreen_result);
  }

  gfx::Rect GetBoundsRect(AXTreeID tree_id,
                          AXNodeID node_id,
                          AXOffscreenResult* offscreen_result) override {
    if (tree_manager_->GetTreeID() != tree_id)
      return gfx::Rect();

    AXNode* node = tree_manager_->GetNode(node_id);
    if (!node)
      return gfx::Rect();

    TestAXNodeHelper* wrapper =
        TestAXNodeHelper::GetOrCreate(tree_manager_->GetTree(), node);
    return wrapper->GetBoundsRect(AXCoordinateSystem::kScreenDIPs,
                                  AXClippingBehavior::kClipped,
                                  offscreen_result);
  }

 private:
  const raw_ptr<SingleAXTreeManager> tree_manager_;
};

class AXRangeTest : public ::testing::Test, public SingleAXTreeManager {
 public:
  const std::u16string EMPTY = u"";
  const std::u16string NEWLINE = u"\n";
  const std::u16string BUTTON = u"Button";
  const std::u16string LINE_1 = u"Line 1";
  const std::u16string LINE_2 = u"Line 2";
  const std::u16string TEXT_FIELD =
      LINE_1.substr().append(NEWLINE).append(LINE_2).append(NEWLINE);
  const std::u16string AFTER_LINE = u"After";
  const std::u16string ALL_TEXT =
      BUTTON.substr().append(TEXT_FIELD).append(AFTER_LINE);

  AXRangeTest();

  AXRangeTest(const AXRangeTest&) = delete;
  AXRangeTest& operator=(const AXRangeTest&) = delete;

  ~AXRangeTest() override = default;

 protected:
  void SetUp() override;

  AXNodeData root_;
  AXNodeData div1_;
  AXNodeData div2_;
  AXNodeData button_;
  AXNodeData check_box1_;
  AXNodeData check_box2_;
  AXNodeData text_field_;
  AXNodeData line_break1_;
  AXNodeData line_break2_;
  AXNodeData static_text1_;
  AXNodeData static_text2_;
  AXNodeData static_text3_;
  AXNodeData inline_box1_;
  AXNodeData inline_box2_;
  AXNodeData inline_box3_;
  AXNodeData inline_box_line_break1_;
  AXNodeData inline_box_line_break2_;
  AXNodeData paragraph_;
  AXNodeData empty_paragraph_;

 private:
  ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior_;
};

// These tests use kSuppressCharacter behavior.
AXRangeTest::AXRangeTest()
    : ax_embedded_object_behavior_(
          AXEmbeddedObjectBehavior::kSuppressCharacter) {}

void AXRangeTest::SetUp() {
  // Set up the AXTree for the following content:
  // ++1 Role::kDialog
  // ++++2 Role::kGenericContainer
  // ++++++3 Role::kButton, name="Button"
  // ++++++4 Role::kGenericContainer
  // ++++++++5 Role::kCheckBox, name="Checkbox 1"
  // ++++++++6 Role::kCheckBox, name="Checkbox 2"
  // ++++7 Role::kTextField
  // ++++++8 Role::kStaticText, name="Line 1"
  // ++++++++9 Role::kInlineTextBox, name="Line 1"
  // ++++++10 Role::kLineBreak, name="\n"
  // ++++++++11 Role::kInlineTextBox, name="\n"
  // ++++++12 Role::kStaticText, name="Line 2"
  // ++++++++13 Role::kInlineTextBox, name="Line 2"
  // ++++++14 Role::kLineBreak, name="\n"
  // ++++++++15 Role::kInlineTextBox, name="\n"
  // ++++16 Role::kParagraph
  // ++++++17 Role::kStaticText, name="After"
  // ++++++++18 Role::kInlineTextBox, name="After"
  // ++++19 Role::kParagraph, IGNORED
  //
  //                      [Root]
  //                  {0, 0, 800, 600}
  //
  // [Button]           [Checkbox 1]         [Checkbox 2]
  // {20, 20, 100x30},  {120, 20, 30x30}     {150, 20, 30x30}
  //
  // [Line 1]           [\n]
  // {20, 50, 30x30}    {50, 50, 0x30}
  //
  // [Line 2]           [\n]
  // {20, 80, 42x30}    {62, 80, 0x30}
  //
  // [After]
  // {20, 110, 50x30}
  //
  // [Empty paragraph]
  // {20, 140, 700, 0}

  root_.id = ROOT_ID;
  div1_.id = DIV1_ID;
  div2_.id = DIV2_ID;
  button_.id = BUTTON_ID;
  check_box1_.id = CHECK_BOX1_ID;
  check_box2_.id = CHECK_BOX2_ID;
  text_field_.id = TEXT_FIELD_ID;
  line_break1_.id = LINE_BREAK1_ID;
  line_break2_.id = LINE_BREAK2_ID;
  static_text1_.id = STATIC_TEXT1_ID;
  static_text2_.id = STATIC_TEXT2_ID;
  static_text3_.id = STATIC_TEXT3_ID;
  inline_box1_.id = INLINE_BOX1_ID;
  inline_box2_.id = INLINE_BOX2_ID;
  inline_box3_.id = INLINE_BOX3_ID;
  inline_box_line_break1_.id = INLINE_BOX_LINE_BREAK1_ID;
  inline_box_line_break2_.id = INLINE_BOX_LINE_BREAK2_ID;
  paragraph_.id = PARAGRAPH_ID;
  empty_paragraph_.id = EMPTY_PARAGRAPH_ID;

  root_.role = ax::mojom::Role::kDialog;
  root_.AddState(ax::mojom::State::kFocusable);
  root_.SetName(ALL_TEXT);
  root_.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);

  div1_.role = ax::mojom::Role::kGenericContainer;
  div1_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  div1_.child_ids.push_back(button_.id);
  div1_.child_ids.push_back(div2_.id);
  root_.child_ids.push_back(div1_.id);

  button_.role = ax::mojom::Role::kButton;
  button_.SetHasPopup(ax::mojom::HasPopup::kMenu);
  button_.SetName("Button");
  button_.SetNameFrom(ax::mojom::NameFrom::kValue);
  button_.SetValue("Button");
  button_.relative_bounds.bounds = gfx::RectF(20, 20, 100, 30);
  button_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
                          check_box1_.id);

  div2_.role = ax::mojom::Role::kGenericContainer;
  div2_.child_ids.push_back(check_box1_.id);
  div2_.child_ids.push_back(check_box2_.id);

  check_box1_.role = ax::mojom::Role::kCheckBox;
  check_box1_.SetCheckedState(ax::mojom::CheckedState::kTrue);
  check_box1_.SetName("Checkbox 1");
  check_box1_.relative_bounds.bounds = gfx::RectF(120, 20, 30, 30);
  check_box1_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
                              button_.id);
  check_box1_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
                              check_box2_.id);

  check_box2_.role = ax::mojom::Role::kCheckBox;
  check_box2_.SetCheckedState(ax::mojom::CheckedState::kTrue);
  check_box2_.SetName("Checkbox 2");
  check_box2_.relative_bounds.bounds = gfx::RectF(150, 20, 30, 30);
  check_box2_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
                              check_box1_.id);

  text_field_.role = ax::mojom::Role::kTextField;
  text_field_.AddState(ax::mojom::State::kEditable);
  text_field_.SetValue(TEXT_FIELD);
  text_field_.AddIntListAttribute(ax::mojom::IntListAttribute::kLineStarts,
                                  std::vector<int32_t>{0, 7});
  text_field_.child_ids.push_back(static_text1_.id);
  text_field_.child_ids.push_back(line_break1_.id);
  text_field_.child_ids.push_back(static_text2_.id);
  text_field_.child_ids.push_back(line_break2_.id);
  root_.child_ids.push_back(text_field_.id);

  static_text1_.role = ax::mojom::Role::kStaticText;
  static_text1_.AddState(ax::mojom::State::kEditable);
  static_text1_.SetName(LINE_1);
  static_text1_.child_ids.push_back(inline_box1_.id);

  inline_box1_.role = ax::mojom::Role::kInlineTextBox;
  inline_box1_.AddState(ax::mojom::State::kEditable);
  inline_box1_.SetName(LINE_1);
  inline_box1_.relative_bounds.bounds = gfx::RectF(20, 50, 30, 30);
  std::vector<int32_t> character_offsets1;
  // The width of each character is 5px.
  character_offsets1.push_back(25);  // "L" {20, 50, 5x30}
  character_offsets1.push_back(30);  // "i" {25, 50, 5x30}
  character_offsets1.push_back(35);  // "n" {30, 50, 5x30}
  character_offsets1.push_back(40);  // "e" {35, 50, 5x30}
  character_offsets1.push_back(45);  // " " {40, 50, 5x30}
  character_offsets1.push_back(50);  // "1" {45, 50, 5x30}
  inline_box1_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
  inline_box1_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                   std::vector<int32_t>{0, 5});
  inline_box1_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                   std::vector<int32_t>{4, 6});
  inline_box1_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
                               line_break1_.id);

  line_break1_.role = ax::mojom::Role::kLineBreak;
  line_break1_.AddState(ax::mojom::State::kEditable);
  line_break1_.SetName(NEWLINE);
  line_break1_.relative_bounds.bounds = gfx::RectF(50, 50, 0, 30);
  line_break1_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
                               inline_box1_.id);
  line_break1_.child_ids.push_back(inline_box_line_break1_.id);

  inline_box_line_break1_.role = ax::mojom::Role::kInlineTextBox;
  inline_box_line_break1_.AddBoolAttribute(
      ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  inline_box_line_break1_.SetName(NEWLINE);
  inline_box_line_break1_.relative_bounds.bounds = gfx::RectF(50, 50, 0, 30);
  inline_box_line_break1_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kCharacterOffsets, {0});
  inline_box_line_break1_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
  inline_box_line_break1_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kWordEnds, std::vector<int32_t>{0});

  static_text2_.role = ax::mojom::Role::kStaticText;
  static_text2_.AddState(ax::mojom::State::kEditable);
  static_text2_.SetName(LINE_2);
  static_text2_.child_ids.push_back(inline_box2_.id);

  inline_box2_.role = ax::mojom::Role::kInlineTextBox;
  inline_box2_.AddState(ax::mojom::State::kEditable);
  inline_box2_.SetName(LINE_2);
  inline_box2_.relative_bounds.bounds = gfx::RectF(20, 80, 42, 30);
  std::vector<int32_t> character_offsets2;
  // The width of each character is 7 px.
  character_offsets2.push_back(27);  // "L" {20, 80, 7x30}
  character_offsets2.push_back(34);  // "i" {27, 80, 7x30}
  character_offsets2.push_back(41);  // "n" {34, 80, 7x30}
  character_offsets2.push_back(48);  // "e" {41, 80, 7x30}
  character_offsets2.push_back(55);  // " " {48, 80, 7x30}
  character_offsets2.push_back(62);  // "2" {55, 80, 7x30}
  inline_box2_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets2);
  inline_box2_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                   std::vector<int32_t>{0, 5});
  inline_box2_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                   std::vector<int32_t>{4, 6});
  inline_box2_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
                               line_break2_.id);

  line_break2_.role = ax::mojom::Role::kLineBreak;
  line_break2_.AddState(ax::mojom::State::kEditable);
  line_break2_.SetName(NEWLINE);
  line_break2_.relative_bounds.bounds = gfx::RectF(62, 80, 0, 30);
  line_break2_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
                               inline_box2_.id);
  line_break2_.child_ids.push_back(inline_box_line_break2_.id);

  inline_box_line_break2_.role = ax::mojom::Role::kInlineTextBox;
  inline_box_line_break2_.AddBoolAttribute(
      ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  inline_box_line_break2_.SetName(NEWLINE);
  inline_box_line_break2_.relative_bounds.bounds = gfx::RectF(62, 80, 0, 30);
  inline_box_line_break2_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kCharacterOffsets, {0});
  inline_box_line_break2_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kWordStarts, std::vector<int32_t>{0});
  inline_box_line_break2_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kWordEnds, std::vector<int32_t>{0});

  paragraph_.role = ax::mojom::Role::kParagraph;
  paragraph_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
                              true);
  paragraph_.child_ids.push_back(static_text3_.id);
  root_.child_ids.push_back(paragraph_.id);

  static_text3_.role = ax::mojom::Role::kStaticText;
  static_text3_.SetName(AFTER_LINE);
  static_text3_.child_ids.push_back(inline_box3_.id);

  inline_box3_.role = ax::mojom::Role::kInlineTextBox;
  inline_box3_.SetName(AFTER_LINE);
  inline_box3_.relative_bounds.bounds = gfx::RectF(20, 110, 50, 30);
  std::vector<int32_t> character_offsets3;
  // The width of each character is 10 px.
  character_offsets3.push_back(30);  // "A" {20, 110, 10x30}
  character_offsets3.push_back(40);  // "f" {30, 110, 10x30}
  character_offsets3.push_back(50);  // "t" {40, 110, 10x30}
  character_offsets3.push_back(60);  // "e" {50, 110, 10x30}
  character_offsets3.push_back(70);  // "r" {60, 110, 10x30}
  inline_box3_.AddIntListAttribute(
      ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets3);
  inline_box3_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                   std::vector<int32_t>{0});
  inline_box3_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                   std::vector<int32_t>{5});

  empty_paragraph_.role = ax::mojom::Role::kParagraph;
  empty_paragraph_.AddState(ax::mojom::State::kIgnored);
  empty_paragraph_.relative_bounds.bounds = gfx::RectF(20, 140, 700, 0);
  root_.child_ids.push_back(empty_paragraph_.id);

  AXTreeUpdate initial_state;
  initial_state.root_id = 1;
  initial_state.nodes = {root_,
                         div1_,
                         button_,
                         div2_,
                         check_box1_,
                         check_box2_,
                         text_field_,
                         static_text1_,
                         inline_box1_,
                         line_break1_,
                         inline_box_line_break1_,
                         static_text2_,
                         inline_box2_,
                         line_break2_,
                         inline_box_line_break2_,
                         paragraph_,
                         static_text3_,
                         inline_box3_,
                         empty_paragraph_};
  initial_state.has_tree_data = true;
  initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
  initial_state.tree_data.title = "Dialog title";

  SetTree(std::make_unique<AXTree>(initial_state));
}

}  // namespace

TEST_F(AXRangeTest, RangeOfContents) {
  const AXNode* root = GetNode(ROOT_ID);
  const TestPositionRange root_range =
      TestPositionRange::RangeOfContents(*root);
  const AXNode* text_field = GetNode(TEXT_FIELD_ID);
  const TestPositionRange text_field_range =
      TestPositionRange::RangeOfContents(*text_field);
  const AXNode* static_text = GetNode(STATIC_TEXT1_ID);
  const TestPositionRange static_text_range =
      TestPositionRange::RangeOfContents(*static_text);
  const AXNode* inline_box = GetNode(INLINE_BOX1_ID);
  const TestPositionRange inline_box_range =
      TestPositionRange::RangeOfContents(*inline_box);

  EXPECT_TRUE(root_range.anchor()->IsTreePosition());
  EXPECT_EQ(root_range.anchor()->GetAnchor(), root);
  EXPECT_EQ(root_range.anchor()->child_index(), 0);
  EXPECT_TRUE(root_range.focus()->IsTreePosition());
  EXPECT_EQ(root_range.focus()->GetAnchor(), root);
  EXPECT_EQ(root_range.focus()->child_index(), 4);

  EXPECT_TRUE(text_field_range.anchor()->IsTextPosition())
      << "Atomic text fields should be leaf nodes hence we get a text "
         "position.";
  EXPECT_EQ(text_field_range.anchor()->GetAnchor(), text_field);
  EXPECT_EQ(text_field_range.anchor()->text_offset(), 0);
  EXPECT_TRUE(text_field_range.focus()->IsTextPosition())
      << "Atomic text fields should be leaf nodes hence we get a text "
         "position.";
  EXPECT_EQ(text_field_range.focus()->GetAnchor(), text_field);
  EXPECT_EQ(text_field_range.focus()->text_offset(), 14)
      << "Should be length of \"Line 1\\nLine 2\\n\".";

  EXPECT_TRUE(static_text_range.anchor()->IsTextPosition());
  EXPECT_EQ(static_text_range.anchor()->GetAnchor(), static_text);
  EXPECT_EQ(static_text_range.anchor()->text_offset(), 0);
  EXPECT_TRUE(static_text_range.focus()->IsTextPosition());
  EXPECT_EQ(static_text_range.focus()->GetAnchor(), static_text);
  EXPECT_EQ(static_text_range.focus()->text_offset(), 6)
      << "Should be length of \"Line 1\".";

  EXPECT_TRUE(inline_box_range.anchor()->IsTextPosition());
  EXPECT_EQ(inline_box_range.anchor()->GetAnchor(), inline_box);
  EXPECT_EQ(inline_box_range.anchor()->text_offset(), 0);
  EXPECT_TRUE(inline_box_range.focus()->IsTextPosition());
  EXPECT_EQ(inline_box_range.focus()->GetAnchor(), inline_box);
  EXPECT_EQ(inline_box_range.focus()->text_offset(), 6)
      << "Should be length of \"Line 1\".";
}

TEST_F(AXRangeTest, EqualityOperators) {
  TestPositionInstance null_position = AXNodePosition::CreateNullPosition();
  TestPositionInstance test_position1 = CreateTextPosition(
      button_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance test_position2 = CreateTextPosition(
      text_field_, 7 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance test_position3 = CreateTextPosition(
      inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  // Invalid ranges (with at least one null endpoint).
  TestPositionRange null_position_and_nullptr(null_position->Clone(), nullptr);
  TestPositionRange nullptr_and_test_position(nullptr, test_position1->Clone());
  TestPositionRange test_position_and_null_position(test_position2->Clone(),
                                                    null_position->Clone());

  TestPositionRange test_positions_1_and_2(test_position1->Clone(),
                                           test_position2->Clone());
  TestPositionRange test_positions_2_and_1(test_position2->Clone(),
                                           test_position1->Clone());
  TestPositionRange test_positions_1_and_3(test_position1->Clone(),
                                           test_position3->Clone());
  TestPositionRange test_positions_2_and_3(test_position2->Clone(),
                                           test_position3->Clone());
  TestPositionRange test_positions_3_and_2(test_position3->Clone(),
                                           test_position2->Clone());

  EXPECT_EQ(null_position_and_nullptr, nullptr_and_test_position);
  EXPECT_EQ(nullptr_and_test_position, test_position_and_null_position);
  EXPECT_NE(null_position_and_nullptr, test_positions_2_and_1);
  EXPECT_NE(test_positions_2_and_1, test_position_and_null_position);
  EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_2);
  EXPECT_NE(test_positions_2_and_1, test_positions_1_and_2);
  EXPECT_EQ(test_positions_3_and_2, test_positions_2_and_3);
  EXPECT_NE(test_positions_1_and_2, test_positions_2_and_3);
  EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_3);
}

TEST_F(AXRangeTest, AsForwardRange) {
  TestPositionRange null_range(AXNodePosition::CreateNullPosition(),
                               AXNodePosition::CreateNullPosition());
  null_range = null_range.AsForwardRange();
  EXPECT_TRUE(null_range.IsNull());

  TestPositionInstance tree_position =
      CreateTreePosition(button_, 0 /* child_index */);
  TestPositionInstance text_position1 = CreateTextPosition(
      line_break1_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance text_position2 = CreateTextPosition(
      inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionRange tree_to_text_range(text_position1->Clone(),
                                       tree_position->Clone());
  tree_to_text_range = tree_to_text_range.AsForwardRange();
  EXPECT_EQ(*tree_position, *tree_to_text_range.anchor());
  EXPECT_EQ(*text_position1, *tree_to_text_range.focus());

  TestPositionRange text_to_text_range(text_position2->Clone(),
                                       text_position1->Clone());
  text_to_text_range = text_to_text_range.AsForwardRange();
  EXPECT_EQ(*text_position1, *text_to_text_range.anchor());
  EXPECT_EQ(*text_position2, *text_to_text_range.focus());
}

TEST_F(AXRangeTest, IsCollapsed) {
  TestPositionRange null_range(AXNodePosition::CreateNullPosition(),
                               AXNodePosition::CreateNullPosition());
  null_range = null_range.AsForwardRange();
  EXPECT_FALSE(null_range.IsCollapsed());

  TestPositionInstance tree_position1 =
      CreateTreePosition(text_field_, 0 /* child_index */);
  // Since there are no children in inline_box1_, the following is essentially
  // an "after text" position which should not compare as equivalent to the
  // above tree position which is a "before text" position inside the text
  // field.
  TestPositionInstance tree_position2 =
      CreateTreePosition(inline_box1_, 0 /* child_index */);

  TestPositionInstance text_position1 = CreateTextPosition(
      static_text1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance text_position2 = CreateTextPosition(
      inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance text_position3 = CreateTextPosition(
      inline_box2_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionRange tree_to_null_range(tree_position1->Clone(),
                                       AXNodePosition::CreateNullPosition());
  EXPECT_TRUE(tree_to_null_range.IsNull());
  EXPECT_FALSE(tree_to_null_range.IsCollapsed());

  TestPositionRange null_to_text_range(AXNodePosition::CreateNullPosition(),
                                       text_position1->Clone());
  EXPECT_TRUE(null_to_text_range.IsNull());
  EXPECT_FALSE(null_to_text_range.IsCollapsed());

  TestPositionRange tree_to_tree_range(tree_position2->Clone(),
                                       tree_position1->Clone());
  EXPECT_FALSE(tree_to_tree_range.IsCollapsed());

  // A tree and a text position that essentially point to the same text offset
  // are equivalent, even if they are anchored to a different node.
  TestPositionRange tree_to_text_range(tree_position1->Clone(),
                                       text_position1->Clone());
  EXPECT_TRUE(tree_to_text_range.IsCollapsed());

  // The following positions are not equivalent since tree_position2 is an
  // "after text" position.
  tree_to_text_range =
      TestPositionRange(tree_position2->Clone(), text_position2->Clone());
  EXPECT_FALSE(tree_to_text_range.IsCollapsed());

  TestPositionRange text_to_text_range(text_position1->Clone(),
                                       text_position1->Clone());
  EXPECT_TRUE(text_to_text_range.IsCollapsed());

  // Two text positions that essentially point to the same text offset are
  // equivalent, even if they are anchored to a different node.
  text_to_text_range =
      TestPositionRange(text_position1->Clone(), text_position2->Clone());
  EXPECT_TRUE(text_to_text_range.IsCollapsed());

  text_to_text_range =
      TestPositionRange(text_position1->Clone(), text_position3->Clone());
  EXPECT_FALSE(text_to_text_range.IsCollapsed());
}

TEST_F(AXRangeTest, BeginAndEndIterators) {
  TestPositionInstance null_position = AXNodePosition::CreateNullPosition();
  TestPositionInstance test_position1 = CreateTextPosition(
      button_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance test_position2 = CreateTextPosition(
      check_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance test_position3 = CreateTextPosition(
      check_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance test_position4 = CreateTextPosition(
      inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionRange nullptr_and_null_position(nullptr, null_position->Clone());
  EXPECT_EQ(TestPositionRange::Iterator(), nullptr_and_null_position.begin());
  EXPECT_EQ(TestPositionRange::Iterator(), nullptr_and_null_position.end());

  TestPositionRange test_position1_and_nullptr(test_position1->Clone(),
                                               nullptr);
  EXPECT_EQ(TestPositionRange::Iterator(), test_position1_and_nullptr.begin());
  EXPECT_EQ(TestPositionRange::Iterator(), test_position1_and_nullptr.end());

  TestPositionRange null_position_and_test_position2(null_position->Clone(),
                                                     test_position2->Clone());
  EXPECT_EQ(TestPositionRange::Iterator(),
            null_position_and_test_position2.begin());
  EXPECT_EQ(TestPositionRange::Iterator(),
            null_position_and_test_position2.end());

  TestPositionRange test_position1_and_test_position2(test_position1->Clone(),
                                                      test_position2->Clone());
  EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
                                        test_position4->Clone()),
            test_position1_and_test_position2.begin());
  EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
                                        test_position3->Clone()),
            test_position1_and_test_position2.begin());
  EXPECT_EQ(TestPositionRange::Iterator(test_position1->Clone(),
                                        test_position2->Clone()),
            test_position1_and_test_position2.begin());
  EXPECT_EQ(TestPositionRange::Iterator(nullptr, test_position2->Clone()),
            test_position1_and_test_position2.end());

  TestPositionRange test_position3_and_test_position4(test_position3->Clone(),
                                                      test_position4->Clone());
  EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
                                        test_position4->Clone()),
            test_position3_and_test_position4.begin());
  EXPECT_NE(TestPositionRange::Iterator(test_position2->Clone(),
                                        test_position4->Clone()),
            test_position3_and_test_position4.begin());
  EXPECT_EQ(TestPositionRange::Iterator(test_position3->Clone(),
                                        test_position4->Clone()),
            test_position3_and_test_position4.begin());
  EXPECT_NE(TestPositionRange::Iterator(nullptr, test_position2->Clone()),
            test_position3_and_test_position4.end());
  EXPECT_NE(TestPositionRange::Iterator(nullptr, test_position3->Clone()),
            test_position3_and_test_position4.end());
  EXPECT_EQ(TestPositionRange::Iterator(nullptr, test_position4->Clone()),
            test_position3_and_test_position4.end());
}

TEST_F(AXRangeTest, LeafTextRangeIteration) {
  TestPositionInstance button_start = CreateTextPosition(
      button_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance button_middle = CreateTextPosition(
      button_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance button_end = CreateTextPosition(
      button_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  // Since a check box is not visible to the text representation, it spans an
  // empty anchor whose start and end positions are the same.
  TestPositionInstance check_box1 = CreateTextPosition(
      check_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance check_box2 = CreateTextPosition(
      check_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance line1_start = CreateTextPosition(
      inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line1_middle = CreateTextPosition(
      inline_box1_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line1_end = CreateTextPosition(
      inline_box1_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance line_break1_start =
      CreateTextPosition(inline_box_line_break1_, 0 /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line_break1_end =
      CreateTextPosition(inline_box_line_break1_, 1 /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance line2_start = CreateTextPosition(
      inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line2_middle = CreateTextPosition(
      inline_box2_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line2_end = CreateTextPosition(
      inline_box2_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance line_break2_start =
      CreateTextPosition(inline_box_line_break2_, 0 /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line_break2_end =
      CreateTextPosition(inline_box_line_break2_, 1 /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance after_line_start = CreateTextPosition(
      inline_box3_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance after_line_end = CreateTextPosition(
      inline_box3_, 5 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance empty_paragraph_start =
      CreateTextPosition(empty_paragraph_, 0 /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance empty_paragraph_end =
      CreateTextPosition(empty_paragraph_, 0 /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);

  std::vector<TestPositionRange> expected_ranges;
  auto TestRangeIterator =
      [&expected_ranges](const TestPositionRange& test_range) {
        std::vector<TestPositionRange> actual_ranges;
        for (TestPositionRange leaf_text_range : test_range) {
          EXPECT_TRUE(leaf_text_range.IsLeafTextRange());
          actual_ranges.emplace_back(std::move(leaf_text_range));
        }

        EXPECT_EQ(expected_ranges.size(), actual_ranges.size());
        size_t element_count =
            std::min(expected_ranges.size(), actual_ranges.size());
        for (size_t i = 0; i < element_count; ++i) {
          EXPECT_EQ(expected_ranges[i], actual_ranges[i]);
          EXPECT_EQ(expected_ranges[i].anchor()->GetAnchor(),
                    actual_ranges[i].anchor()->GetAnchor());
        }
      };

  // Iterating over a null range; note that expected_ranges is empty.
  TestRangeIterator(TestPositionRange(nullptr, nullptr));

  TestPositionRange non_null_degenerate_range(check_box1->Clone(),
                                              check_box1->Clone());
  expected_ranges.clear();
  expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
  TestRangeIterator(non_null_degenerate_range);

  TestPositionRange empty_text_forward_range(button_end->Clone(),
                                             line1_start->Clone());
  TestPositionRange empty_text_backward_range(line1_start->Clone(),
                                              button_end->Clone());
  expected_ranges.clear();
  expected_ranges.emplace_back(button_end->Clone(), button_end->Clone());
  expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
  expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
  expected_ranges.emplace_back(line1_start->Clone(), line1_start->Clone());
  TestRangeIterator(empty_text_forward_range);
  TestRangeIterator(empty_text_backward_range);

  TestPositionRange entire_anchor_forward_range(button_start->Clone(),
                                                button_end->Clone());
  TestPositionRange entire_anchor_backward_range(button_end->Clone(),
                                                 button_start->Clone());
  expected_ranges.clear();
  expected_ranges.emplace_back(button_start->Clone(), button_end->Clone());
  TestRangeIterator(entire_anchor_forward_range);
  TestRangeIterator(entire_anchor_backward_range);

  TestPositionRange across_anchors_forward_range(button_middle->Clone(),
                                                 line1_middle->Clone());
  TestPositionRange across_anchors_backward_range(line1_middle->Clone(),
                                                  button_middle->Clone());
  expected_ranges.clear();
  expected_ranges.emplace_back(button_middle->Clone(), button_end->Clone());
  expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
  expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
  expected_ranges.emplace_back(line1_start->Clone(), line1_middle->Clone());
  TestRangeIterator(across_anchors_forward_range);
  TestRangeIterator(across_anchors_backward_range);

  TestPositionRange starting_at_end_position_forward_range(
      line1_end->Clone(), line2_middle->Clone());
  TestPositionRange starting_at_end_position_backward_range(
      line2_middle->Clone(), line1_end->Clone());
  expected_ranges.clear();
  expected_ranges.emplace_back(line1_end->Clone(), line1_end->Clone());
  expected_ranges.emplace_back(line_break1_start->Clone(),
                               line_break1_end->Clone());
  expected_ranges.emplace_back(line2_start->Clone(), line2_middle->Clone());
  TestRangeIterator(starting_at_end_position_forward_range);
  TestRangeIterator(starting_at_end_position_backward_range);

  TestPositionRange ending_at_start_position_forward_range(
      line1_middle->Clone(), line2_start->Clone());
  TestPositionRange ending_at_start_position_backward_range(
      line2_start->Clone(), line1_middle->Clone());
  expected_ranges.clear();
  expected_ranges.emplace_back(line1_middle->Clone(), line1_end->Clone());
  expected_ranges.emplace_back(line_break1_start->Clone(),
                               line_break1_end->Clone());
  expected_ranges.emplace_back(line2_start->Clone(), line2_start->Clone());
  TestRangeIterator(ending_at_start_position_forward_range);
  TestRangeIterator(ending_at_start_position_backward_range);

  TestPositionInstance range_start =
      CreateTreePosition(root_, 0 /* child_index */);
  TestPositionInstance range_end =
      CreateTextPosition(root_, ALL_TEXT.length() /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);

  TestPositionRange entire_test_forward_range(range_start->Clone(),
                                              range_end->Clone());
  TestPositionRange entire_test_backward_range(range_end->Clone(),
                                               range_start->Clone());
  expected_ranges.clear();
  expected_ranges.emplace_back(button_start->Clone(), button_end->Clone());
  expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
  expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
  expected_ranges.emplace_back(line1_start->Clone(), line1_end->Clone());
  expected_ranges.emplace_back(line_break1_start->Clone(),
                               line_break1_end->Clone());
  expected_ranges.emplace_back(line2_start->Clone(), line2_end->Clone());
  expected_ranges.emplace_back(line_break2_start->Clone(),
                               line_break2_end->Clone());
  expected_ranges.emplace_back(after_line_start->Clone(),
                               after_line_end->Clone());
  expected_ranges.emplace_back(empty_paragraph_start->Clone(),
                               empty_paragraph_end->Clone());
  TestRangeIterator(entire_test_forward_range);
  TestRangeIterator(entire_test_backward_range);
}

TEST_F(AXRangeTest, GetTextWithWholeObjects) {
  // Create a range starting from the button object and ending at the last
  // character of the root, i.e. at the last character of the second line in the
  // text field.
  TestPositionInstance start = CreateTreePosition(root_, 0 /* child_index */);
  TestPositionInstance end =
      CreateTextPosition(root_, ALL_TEXT.length() /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange forward_range(start->Clone(), end->Clone());
  EXPECT_EQ(ALL_TEXT, forward_range.GetText(
                          AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                          AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange backward_range(std::move(end), std::move(start));
  EXPECT_EQ(ALL_TEXT, backward_range.GetText(
                          AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                          AXEmbeddedObjectBehavior::kSuppressCharacter));

  // Button
  start = CreateTextPosition(button_, 0 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(start->IsTextPosition());
  end = CreateTextPosition(button_, BUTTON.length() /* text_offset */,
                           ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange button_range(start->Clone(), end->Clone());
  EXPECT_EQ(BUTTON, button_range.GetText(
                        AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                        AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange button_range_backward(std::move(end), std::move(start));
  EXPECT_EQ(BUTTON, button_range_backward.GetText(
                        AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                        AXEmbeddedObjectBehavior::kSuppressCharacter));

  // text_field_
  start = CreateTextPosition(text_field_, 0 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  end = CreateTextPosition(text_field_, TEXT_FIELD.length() /* text_offset */,
                           ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(start->IsTextPosition());
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange text_field_range(start->Clone(), end->Clone());
  EXPECT_EQ(TEXT_FIELD,
            text_field_range.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange text_field_range_backward(std::move(end), std::move(start));
  EXPECT_EQ(TEXT_FIELD,
            text_field_range_backward.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));

  // static_text1_
  start = CreateTextPosition(static_text1_, 0 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(start->IsTextPosition());
  end = CreateTextPosition(static_text1_, LINE_1.length() /* text_offset */,
                           ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange static_text1_range(start->Clone(), end->Clone());
  EXPECT_EQ(LINE_1, static_text1_range.GetText(
                        AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                        AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange static_text1_range_backward(std::move(end),
                                                std::move(start));
  EXPECT_EQ(LINE_1, static_text1_range_backward.GetText(
                        AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                        AXEmbeddedObjectBehavior::kSuppressCharacter));

  // static_text2_
  start = CreateTextPosition(static_text2_, 0 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(start->IsTextPosition());
  end = CreateTextPosition(static_text2_, LINE_2.length() /* text_offset */,
                           ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange static_text2_range(start->Clone(), end->Clone());
  EXPECT_EQ(LINE_2, static_text2_range.GetText(
                        AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                        AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange static_text2_range_backward(std::move(end),
                                                std::move(start));
  EXPECT_EQ(LINE_2, static_text2_range_backward.GetText(
                        AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                        AXEmbeddedObjectBehavior::kSuppressCharacter));

  // static_text1_ to static_text2_
  std::u16string text_between_text1_start_and_text2_end =
      LINE_1.substr().append(NEWLINE).append(LINE_2);
  start = CreateTextPosition(static_text1_, 0 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(start->IsTextPosition());
  end = CreateTextPosition(static_text2_, LINE_2.length() /* text_offset */,
                           ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange static_text_range(start->Clone(), end->Clone());
  EXPECT_EQ(text_between_text1_start_and_text2_end,
            static_text_range.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange static_text_range_backward(std::move(end),
                                               std::move(start));
  EXPECT_EQ(text_between_text1_start_and_text2_end,
            static_text_range_backward.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));

  // root_ to static_text2_'s end
  std::u16string text_up_to_text2_end =
      BUTTON.substr(0).append(LINE_1).append(NEWLINE).append(LINE_2);
  start = CreateTreePosition(root_, 0 /* child_index */);
  end = CreateTextPosition(static_text2_, LINE_2.length() /* text_offset */,
                           ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange root_to_static2_text_range(start->Clone(), end->Clone());
  EXPECT_EQ(text_up_to_text2_end,
            root_to_static2_text_range.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange root_to_static2_text_range_backward(std::move(end),
                                                        std::move(start));
  EXPECT_EQ(text_up_to_text2_end,
            root_to_static2_text_range_backward.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));

  // root_ to static_text2_'s start
  std::u16string text_up_to_text2_start =
      BUTTON.substr(0).append(LINE_1).append(NEWLINE);
  start = CreateTreePosition(root_, 0 /* child_index */);
  end = CreateTreePosition(static_text2_, 0 /* child_index */);
  TestPositionRange root_to_static2_tree_range(start->Clone(), end->Clone());
  EXPECT_EQ(text_up_to_text2_start,
            root_to_static2_tree_range.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange root_to_static2_tree_range_backward(std::move(end),
                                                        std::move(start));
  EXPECT_EQ(text_up_to_text2_start,
            root_to_static2_tree_range_backward.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));
}

TEST_F(AXRangeTest, GetTextWithTextOffsets) {
  std::u16string most_text = BUTTON.substr(2).append(TEXT_FIELD.substr(0, 11));
  // Create a range starting from the button object and ending two characters
  // before the end of the root.
  TestPositionInstance start = CreateTextPosition(
      button_.id, 2 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(start->IsTextPosition());
  TestPositionInstance end = CreateTextPosition(
      static_text2_, 4 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange forward_range(start->Clone(), end->Clone());
  EXPECT_EQ(most_text, forward_range.GetText(
                           AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                           AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange backward_range(std::move(end), std::move(start));
  EXPECT_EQ(most_text, backward_range.GetText(
                           AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                           AXEmbeddedObjectBehavior::kSuppressCharacter));

  // root_ to static_text2_'s start with offsets
  std::u16string text_up_to_text2_tree_start =
      BUTTON.substr(0).append(TEXT_FIELD.substr(0, 10));
  start = CreateTreePosition(root_, 0 /* child_index */);
  end = CreateTextPosition(static_text2_, 3 /* text_offset */,
                           ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange root_to_static2_tree_range(start->Clone(), end->Clone());
  EXPECT_EQ(text_up_to_text2_tree_start,
            root_to_static2_tree_range.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));
  TestPositionRange root_to_static2_tree_range_backward(std::move(end),
                                                        std::move(start));
  EXPECT_EQ(text_up_to_text2_tree_start,
            root_to_static2_tree_range_backward.GetText(
                AXTextConcatenationBehavior::kWithoutParagraphBreaks,
                AXEmbeddedObjectBehavior::kSuppressCharacter));
}

TEST_F(AXRangeTest, GetTextWithEmptyRanges) {
  // empty string with non-leaf tree position
  TestPositionInstance start = CreateTreePosition(root_, 0 /* child_index */);
  TestPositionRange non_leaf_tree_range(start->Clone(), start->Clone());
  EXPECT_EQ(EMPTY, non_leaf_tree_range.GetText());

  // empty string with leaf tree position
  start = CreateTreePosition(inline_box1_, 0 /* child_index */);
  TestPositionRange leaf_empty_range(start->Clone(), start->Clone());
  EXPECT_EQ(EMPTY, leaf_empty_range.GetText());

  // empty string with leaf text position and no offset
  start = CreateTextPosition(inline_box1_, 0 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  TestPositionRange leaf_text_no_offset(start->Clone(), start->Clone());
  EXPECT_EQ(EMPTY, leaf_text_no_offset.GetText());

  // empty string with leaf text position with offset
  start = CreateTextPosition(inline_box1_, 3 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  TestPositionRange leaf_text_offset(start->Clone(), start->Clone());
  EXPECT_EQ(EMPTY, leaf_text_offset.GetText());

  // empty string with non-leaf text with no offset
  start = CreateTextPosition(root_, 0 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  TestPositionRange non_leaf_text_no_offset(start->Clone(), start->Clone());
  EXPECT_EQ(EMPTY, non_leaf_text_no_offset.GetText());

  // empty string with non-leaf text position with offset
  start = CreateTextPosition(root_, 3 /* text_offset */,
                             ax::mojom::TextAffinity::kDownstream);
  TestPositionRange non_leaf_text_offset(start->Clone(), start->Clone());
  EXPECT_EQ(EMPTY, non_leaf_text_offset.GetText());

  // empty string with same position between two anchors, but different offsets
  TestPositionInstance after_end = CreateTextPosition(
      line_break1_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance before_start = CreateTextPosition(
      static_text2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionRange same_position_different_anchors_forward(
      after_end->Clone(), before_start->Clone());
  EXPECT_EQ(EMPTY, same_position_different_anchors_forward.GetText());
  TestPositionRange same_position_different_anchors_backward(
      before_start->Clone(), after_end->Clone());
  EXPECT_EQ(EMPTY, same_position_different_anchors_backward.GetText());
}

TEST_F(AXRangeTest, GetTextAddingNewlineBetweenParagraphs) {
  // There are three newlines between the button and the text field. The first
  // two are emitted because there are two empty checkboxes following the button
  // on the same line, i.e. two checkboxes without any text contents. Each empty
  // object forms a paragraph boundary, so that such an object will be easily
  // discernible by a screen reader user. The third newline is caused by the
  // fact that the text field is on the next line.
  const std::u16string button_end_to_line1_start =
      NEWLINE.substr().append(NEWLINE).append(NEWLINE);

  TestPositionInstance button_start = CreateTextPosition(
      button_.id, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance button_end = CreateTextPosition(
      button_.id, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance line1_start = CreateTextPosition(
      inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line1_end = CreateTextPosition(
      inline_box1_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance line2_start = CreateTextPosition(
      inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line2_end = CreateTextPosition(
      inline_box2_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance after_line_start = CreateTextPosition(
      inline_box3_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance after_line_end = CreateTextPosition(
      inline_box3_, 5 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  auto TestGetTextForRange = [](TestPositionInstance range_start,
                                TestPositionInstance range_end,
                                const std::u16string& expected_text,
                                const size_t expected_appended_newlines_count) {
    TestPositionRange forward_test_range(range_start->Clone(),
                                         range_end->Clone());
    TestPositionRange backward_test_range(std::move(range_end),
                                          std::move(range_start));
    size_t appended_newlines_count = 0;
    EXPECT_EQ(expected_text,
              forward_test_range.GetText(
                  AXTextConcatenationBehavior::kWithParagraphBreaks,
                  g_ax_embedded_object_behavior, -1, false,
                  &appended_newlines_count));
    EXPECT_EQ(expected_appended_newlines_count, appended_newlines_count);
    EXPECT_EQ(expected_text,
              backward_test_range.GetText(
                  AXTextConcatenationBehavior::kWithParagraphBreaks,
                  g_ax_embedded_object_behavior, -1, false,
                  &appended_newlines_count));
    EXPECT_EQ(expected_appended_newlines_count, appended_newlines_count);
  };

  std::u16string button_start_to_line1_end =
      BUTTON.substr().append(button_end_to_line1_start).append(LINE_1);
  TestGetTextForRange(button_start->Clone(), line1_end->Clone(),
                      button_start_to_line1_end,
                      /* expected_appended_newlines_count */ 3);
  std::u16string button_start_to_line1_start =
      BUTTON.substr().append(button_end_to_line1_start);
  TestGetTextForRange(button_start->Clone(), line1_start->Clone(),
                      button_start_to_line1_start,
                      /* expected_appended_newlines_count */ 3);
  std::u16string button_end_to_line1_end =
      button_end_to_line1_start.substr().append(LINE_1);
  TestGetTextForRange(button_end->Clone(), line1_end->Clone(),
                      button_end_to_line1_end,
                      /* expected_appended_newlines_count */ 3);
  TestGetTextForRange(button_end->Clone(), line1_start->Clone(),
                      button_end_to_line1_start,
                      /* expected_appended_newlines_count */ 3);

  std::u16string line2_start_to_after_line_end =
      LINE_2.substr().append(NEWLINE).append(AFTER_LINE);
  TestGetTextForRange(line2_start->Clone(), after_line_end->Clone(),
                      line2_start_to_after_line_end, 0);
  std::u16string line2_start_to_after_line_start =
      LINE_2.substr().append(NEWLINE);
  TestGetTextForRange(line2_start->Clone(), after_line_start->Clone(),
                      line2_start_to_after_line_start, 0);
  std::u16string line2_end_to_after_line_end =
      NEWLINE.substr().append(AFTER_LINE);
  TestGetTextForRange(line2_end->Clone(), after_line_end->Clone(),
                      line2_end_to_after_line_end, 0);
  std::u16string line2_end_to_after_line_start = NEWLINE;
  TestGetTextForRange(line2_end->Clone(), after_line_start->Clone(),
                      line2_end_to_after_line_start, 0);

  std::u16string all_text = BUTTON.substr()
                                .append(button_end_to_line1_start)
                                .append(TEXT_FIELD)
                                .append(AFTER_LINE);
  TestPositionInstance start = CreateTextPosition(
      root_.id, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance end =
      CreateTextPosition(root_, ALL_TEXT.length() /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);
  TestGetTextForRange(std::move(start), std::move(end), all_text,
                      /* expected_appended_newlines_count */ 3);
}

TEST_F(AXRangeTest, GetTextWithMaxCount) {
  TestPositionInstance line1_start = CreateTextPosition(
      inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line2_end = CreateTextPosition(
      inline_box2_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionRange test_range(line1_start->Clone(), line2_end->Clone());
  EXPECT_EQ(
      LINE_1.substr(0, 2),
      test_range.GetText(AXTextConcatenationBehavior::kWithParagraphBreaks,
                         g_ax_embedded_object_behavior, 2));

  // Test the case where an appended newline falls right at max_count.
  EXPECT_EQ(
      LINE_1.substr().append(NEWLINE),
      test_range.GetText(AXTextConcatenationBehavior::kWithParagraphBreaks,
                         g_ax_embedded_object_behavior, 7));

  // Test passing -1 for max_count.
  EXPECT_EQ(
      LINE_1.substr().append(NEWLINE).append(LINE_2),
      test_range.GetText(AXTextConcatenationBehavior::kWithParagraphBreaks,
                         g_ax_embedded_object_behavior, -1));
}

TEST_F(AXRangeTest, GetTextWithList) {
  const std::u16string kListMarker1 = u"1. ";
  const std::u16string kListItemContent = u"List item 1";
  const std::u16string kListMarker2 = u"2. ";
  const std::u16string kAfterList = u"After list";
  const std::u16string kAllText = kListMarker1.substr()
                                      .append(kListItemContent)
                                      .append(NEWLINE)
                                      .append(kListMarker2)
                                      .append(NEWLINE)
                                      .append(kAfterList);
  // This test expects:
  // "1. List item 1
  //  2.
  //  After list"
  // for the following AXTree:
  // ++1 kRootWebArea
  // ++++2 kList
  // ++++++3 kListItem
  // ++++++++4 kListMarker
  // ++++++++++5 kStaticText
  // ++++++++++++6 kInlineTextBox "1. "
  // ++++++++7 kStaticText
  // ++++++++++8 kInlineTextBox "List item 1"
  // ++++++9 kListItem
  // ++++++++10 kListMarker
  // +++++++++++11 kStaticText
  // ++++++++++++++12 kInlineTextBox "2. "
  // ++++13 kStaticText
  // +++++++14 kInlineTextBox "After list"
  AXNodeData root;
  AXNodeData list;
  AXNodeData list_item1;
  AXNodeData list_item2;
  AXNodeData list_marker1;
  AXNodeData list_marker2;
  AXNodeData inline_box1;
  AXNodeData inline_box2;
  AXNodeData inline_box3;
  AXNodeData inline_box4;
  AXNodeData static_text1;
  AXNodeData static_text2;
  AXNodeData static_text3;
  AXNodeData static_text4;

  root.id = 1;
  list.id = 2;
  list_item1.id = 3;
  list_marker1.id = 4;
  static_text1.id = 5;
  inline_box1.id = 6;
  static_text2.id = 7;
  inline_box2.id = 8;
  list_item2.id = 9;
  list_marker2.id = 10;
  static_text3.id = 11;
  inline_box3.id = 12;
  static_text4.id = 13;
  inline_box4.id = 14;

  root.role = ax::mojom::Role::kRootWebArea;
  root.child_ids = {list.id, static_text4.id};

  list.role = ax::mojom::Role::kList;
  list.child_ids = {list_item1.id, list_item2.id};

  list_item1.role = ax::mojom::Role::kListItem;
  list_item1.child_ids = {list_marker1.id, static_text2.id};
  list_item1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
                              true);

  list_marker1.role = ax::mojom::Role::kListMarker;
  list_marker1.child_ids = {static_text1.id};

  static_text1.role = ax::mojom::Role::kStaticText;
  static_text1.SetName(kListMarker1);
  static_text1.child_ids = {inline_box1.id};

  inline_box1.role = ax::mojom::Role::kInlineTextBox;
  inline_box1.SetName(kListMarker1);

  static_text2.role = ax::mojom::Role::kStaticText;
  static_text2.SetName(kListItemContent);
  static_text2.child_ids = {inline_box2.id};

  inline_box2.role = ax::mojom::Role::kInlineTextBox;
  inline_box2.SetName(kListItemContent);

  list_item2.role = ax::mojom::Role::kListItem;
  list_item2.child_ids = {list_marker2.id};
  list_item2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
                              true);

  list_marker2.role = ax::mojom::Role::kListMarker;
  list_marker2.child_ids = {static_text3.id};

  static_text3.role = ax::mojom::Role::kStaticText;
  static_text3.SetName(kListMarker2);
  static_text3.child_ids = {inline_box3.id};

  inline_box3.role = ax::mojom::Role::kInlineTextBox;
  inline_box3.SetName(kListMarker2);

  static_text4.role = ax::mojom::Role::kStaticText;
  static_text4.SetName(kAfterList);
  static_text4.child_ids = {inline_box4.id};

  inline_box4.role = ax::mojom::Role::kInlineTextBox;
  inline_box4.SetName(kAfterList);

  AXTreeUpdate initial_state;
  initial_state.root_id = root.id;
  initial_state.nodes = {root,         list,         list_item1,   list_marker1,
                         static_text1, inline_box1,  static_text2, inline_box2,
                         list_item2,   list_marker2, static_text3, inline_box3,
                         static_text4, inline_box4};
  initial_state.has_tree_data = true;
  initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
  initial_state.tree_data.title = "Dialog title";

  SetTree(std::make_unique<AXTree>(initial_state));

  TestPositionInstance start = CreateTextPosition(
      inline_box1, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(start->IsTextPosition());
  TestPositionInstance end = CreateTextPosition(
      inline_box4, 10 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  ASSERT_TRUE(end->IsTextPosition());
  TestPositionRange forward_range(start->Clone(), end->Clone());
  EXPECT_EQ(kAllText, forward_range.GetText(
                          AXTextConcatenationBehavior::kWithParagraphBreaks));
  TestPositionRange backward_range(std::move(end), std::move(start));
  EXPECT_EQ(kAllText, backward_range.GetText(
                          AXTextConcatenationBehavior::kWithParagraphBreaks));
}

TEST_F(AXRangeTest, GetRects) {
  TestAXRangeScreenRectDelegate delegate(this);

  // Setting up ax ranges for testing.
  TestPositionInstance button = CreateTextPosition(
      button_.id, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance check_box1 = CreateTextPosition(
      check_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance check_box2 = CreateTextPosition(
      check_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance line1_start = CreateTextPosition(
      inline_box1_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line1_second_char = CreateTextPosition(
      inline_box1_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line1_middle = CreateTextPosition(
      inline_box1_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line1_second_to_last_char = CreateTextPosition(
      inline_box1_, 5 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line1_end = CreateTextPosition(
      inline_box1_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance line2_start = CreateTextPosition(
      inline_box2_, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line2_second_char = CreateTextPosition(
      inline_box2_, 1 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line2_middle = CreateTextPosition(
      inline_box2_, 3 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line2_second_to_last_char = CreateTextPosition(
      inline_box2_, 5 /* text_offset */, ax::mojom::TextAffinity::kDownstream);
  TestPositionInstance line2_end = CreateTextPosition(
      inline_box2_, 6 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance empty_paragraph_end =
      CreateTextPosition(empty_paragraph_, 0 /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);

  // Since a button is not visible to the text representation, it spans an
  // empty anchor whose start and end positions are the same.
  TestPositionRange button_range(button->Clone(), button->Clone());
  std::vector<gfx::Rect> expected_screen_rects = {gfx::Rect(20, 20, 100, 30)};
  EXPECT_THAT(button_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Since a check box is not visible to the text representation, it spans an
  // empty anchor whose start and end positions are the same.
  TestPositionRange check_box1_range(check_box1->Clone(), check_box1->Clone());
  expected_screen_rects = {gfx::Rect(120, 20, 30, 30)};
  EXPECT_THAT(check_box1_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding boxes of the button and both checkboxes.
  TestPositionRange button_check_box2_range(button->Clone(),
                                            check_box2->Clone());
  expected_screen_rects = {gfx::Rect(20, 20, 100, 30),
                           gfx::Rect(120, 20, 30, 30),
                           gfx::Rect(150, 20, 30, 30)};
  EXPECT_THAT(button_check_box2_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 1's degenerate range at its start.
  //  0 1 2 3 4 5
  // |L|i|n|e| |1|
  // ||
  TestPositionRange line1_degenerate_range(line1_start->Clone(),
                                           line1_start->Clone());
  expected_screen_rects = {gfx::Rect(20, 50, 1, 30)};
  EXPECT_THAT(line1_degenerate_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 1, its whole range.
  //  0 1 2 3 4 5
  // |L|i|n|e| |1|
  // |-----------|
  TestPositionRange line1_whole_range(line1_start->Clone(), line1_end->Clone());
  expected_screen_rects = {gfx::Rect(20, 50, 30, 30)};
  EXPECT_THAT(line1_whole_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 1, its first half range.
  //  0 1 2 3 4 5
  // |L|i|n|e| |1|
  // |-----|
  TestPositionRange line1_first_half_range(line1_start->Clone(),
                                           line1_middle->Clone());
  expected_screen_rects = {gfx::Rect(20, 50, 15, 30)};
  EXPECT_THAT(line1_first_half_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 1, its second half range.
  //  0 1 2 3 4 5
  // |L|i|n|e| |1|
  //       |-----|
  TestPositionRange line1_second_half_range(line1_middle->Clone(),
                                            line1_end->Clone());
  expected_screen_rects = {gfx::Rect(35, 50, 15, 30)};
  EXPECT_THAT(line1_second_half_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 1, its mid range.
  //  0 1 2 3 4 5
  // |L|i|n|e| |1|
  //   |-------|
  TestPositionRange line1_mid_range(line1_second_char->Clone(),
                                    line1_second_to_last_char->Clone());
  expected_screen_rects = {gfx::Rect(25, 50, 20, 30)};
  EXPECT_THAT(line1_mid_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 2, its whole range.
  //  0 1 2 3 4 5
  // |L|i|n|e| |2|
  // |-----------|
  TestPositionRange line2_whole_range(line2_start->Clone(), line2_end->Clone());
  expected_screen_rects = {gfx::Rect(20, 80, 42, 30)};
  EXPECT_THAT(line2_whole_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 2, its first half range.
  //  0 1 2 3 4 5
  // |L|i|n|e| |2|
  // |-----|
  TestPositionRange line2_first_half_range(line2_start->Clone(),
                                           line2_middle->Clone());
  expected_screen_rects = {gfx::Rect(20, 80, 21, 30)};
  EXPECT_THAT(line2_first_half_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 2, its second half range.
  //  0 1 2 3 4 5
  // |L|i|n|e| |2|
  //       |-----|
  TestPositionRange line2_second_half_range(line2_middle->Clone(),
                                            line2_end->Clone());
  expected_screen_rects = {gfx::Rect(41, 80, 21, 30)};
  EXPECT_THAT(line2_second_half_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of text line 2, its mid range.
  //  0 1 2 3 4 5
  // |L|i|n|e| |2|
  //   |-------|
  TestPositionRange line2_mid_range(line2_second_char->Clone(),
                                    line2_second_to_last_char->Clone());
  expected_screen_rects = {gfx::Rect(27, 80, 28, 30)};
  EXPECT_THAT(line2_mid_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding box of degenerate range of text line 2, before its
  // second character.
  //  0 1 2 3 4 5
  // |L|i|n|e| |2|
  //   ||
  TestPositionRange line2_degenerate_range(line2_second_char->Clone(),
                                           line2_second_char->Clone());
  expected_screen_rects = {gfx::Rect(27, 80, 1, 30)};
  EXPECT_THAT(line2_degenerate_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding boxes of text line 1 and line 2, the entire range.
  // |L|i|n|e| |1|\n|L|i|n|e| |2|\n|
  // |--------------------------|
  TestPositionRange line1_line2_whole_range(line1_start->Clone(),
                                            line2_end->Clone());
  expected_screen_rects = {gfx::Rect(20, 50, 30, 30),
                           gfx::Rect(20, 80, 42, 30)};
  EXPECT_THAT(line1_line2_whole_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding boxes of the range that spans from the middle of text
  // line 1 to the middle of text line 2.
  // |L|i|n|e| |1|\n|L|i|n|e| |2|\n|
  //       |--------------|
  TestPositionRange line1_line2_mid_range(line1_middle->Clone(),
                                          line2_middle->Clone());
  expected_screen_rects = {gfx::Rect(35, 50, 15, 30),
                           gfx::Rect(20, 80, 21, 30)};
  EXPECT_THAT(line1_line2_mid_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding boxes of the range that spans from the checkbox 2
  // ("invisible" in the text representation) to the middle of text line 2.
  // |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r<p>
  //                      |-------------------------------|
  TestPositionRange check_box2_line2_mid_range(check_box2->Clone(),
                                               line2_middle->Clone());
  expected_screen_rects = {gfx::Rect(150, 20, 30, 30),
                           gfx::Rect(20, 50, 30, 30),
                           gfx::Rect(20, 80, 21, 30)};
  EXPECT_THAT(check_box2_line2_mid_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Retrieving bounding boxes of the range spanning the entire document.
  // |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r<p>
  // |-------------------------------------------------------------------------|
  TestPositionRange entire_test_range(button->Clone(),
                                      empty_paragraph_end->Clone());
  expected_screen_rects = {
      gfx::Rect(20, 20, 100, 30), gfx::Rect(120, 20, 30, 30),
      gfx::Rect(150, 20, 30, 30), gfx::Rect(20, 50, 30, 30),
      gfx::Rect(20, 80, 42, 30),  gfx::Rect(20, 110, 50, 30)};
  EXPECT_THAT(entire_test_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));
}

TEST_F(AXRangeTest, GetRectsOffscreen) {
  // Set up root node bounds/viewport size  to {0, 50, 800x60}, so that only
  // some text will be onscreen the rest will be offscreen.
  AXNodeData old_root_node_data = GetRoot()->data();
  AXNodeData new_root_node_data = old_root_node_data;
  new_root_node_data.relative_bounds.bounds = gfx::RectF(0, 50, 800, 60);
  GetRoot()->SetData(new_root_node_data);

  TestAXRangeScreenRectDelegate delegate(this);

  TestPositionInstance button = CreateTextPosition(
      button_.id, 0 /* text_offset */, ax::mojom::TextAffinity::kDownstream);

  TestPositionInstance empty_paragraph_end =
      CreateTextPosition(empty_paragraph_, 0 /* text_offset */,
                         ax::mojom::TextAffinity::kDownstream);

  // [Button]           [Checkbox 1]         [Checkbox 2]
  // {20, 20, 100x30},  {120, 20, 30x30}     {150, 20, 30x30}
  //                                              ---
  // [Line 1]           [\n]                      |
  // {20, 50, 30x30}                              | view port, onscreen
  //                                              | {0, 50, 800x60}
  // [Line 2]           [\n]                      |
  // {20, 80, 42x30}                              |
  //                                              ---
  // [After]
  // {20, 110, 50x30}
  //
  // [Empty paragraph]
  //
  // Retrieving bounding boxes of the range spanning the entire document.
  // |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r<P>
  // |-------------------------------------------------------------------------|
  TestPositionRange entire_test_range(button->Clone(),
                                      empty_paragraph_end->Clone());
  std::vector<gfx::Rect> expected_screen_rects = {gfx::Rect(20, 50, 30, 30),
                                                  gfx::Rect(20, 80, 42, 30)};
  EXPECT_THAT(entire_test_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));

  // Reset the root node bounds/viewport size back to {0, 0, 800x600}, and
  // verify all elements should be onscreen.
  GetRoot()->SetData(old_root_node_data);
  expected_screen_rects = {
      gfx::Rect(20, 20, 100, 30), gfx::Rect(120, 20, 30, 30),
      gfx::Rect(150, 20, 30, 30), gfx::Rect(20, 50, 30, 30),
      gfx::Rect(20, 80, 42, 30),  gfx::Rect(20, 110, 50, 30)};
  EXPECT_THAT(entire_test_range.GetRects(&delegate),
              ::testing::ContainerEq(expected_screen_rects));
}

}  // namespace ui