text: // Copyright 2019 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 "ui/accessibility/platform/ax_platform_node_win_unittest.h"
  
  #include <UIAutomationClient.h>
  #include <UIAutomationCoreApi.h>
  
  #include <memory>
  #include <utility>
  
  #include "base/win/atl.h"
  #include "base/win/scoped_bstr.h"
  #include "base/win/scoped_safearray.h"
  #include "base/win/scoped_variant.h"
  #include "ui/accessibility/platform/ax_fragment_root_win.h"
  #include "ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h"
  using Microsoft::WRL::ComPtr;
  
  namespace ui {
  
  // Helper macros for UIAutomation HRESULT expectations
  #define EXPECT_UIA_ELEMENTNOTAVAILABLE(expr) \
    EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), (expr))
  #define EXPECT_UIA_INVALIDOPERATION(expr) \
    EXPECT_EQ(static_cast<HRESULT>(UIA_E_INVALIDOPERATION), (expr))
  #define EXPECT_UIA_ELEMENTNOTENABLED(expr) \
    EXPECT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTENABLED), (expr))
  #define EXPECT_UIA_NOTSUPPORTED(expr) \
    EXPECT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED), (expr))
  
  #define ASSERT_UIA_ELEMENTNOTAVAILABLE(expr) \
    ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), (expr))
  #define ASSERT_UIA_INVALIDOPERATION(expr) \
    ASSERT_EQ(static_cast<HRESULT>(UIA_E_INVALIDOPERATION), (expr))
  #define ASSERT_UIA_ELEMENTNOTENABLED(expr) \
    ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTENABLED), (expr))
  #define ASSERT_UIA_NOTSUPPORTED(expr) \
    ASSERT_EQ(static_cast<HRESULT>(UIA_E_NOTSUPPORTED), (expr))
  
  #define EXPECT_UIA_SAFEARRAY_EQ(safearray, expected_property_values)   \
    {                                                                    \
      using T = typename decltype(expected_property_values)::value_type; \
      EXPECT_EQ(sizeof(T), ::SafeArrayGetElemsize(safearray));           \
      EXPECT_EQ(1u, SafeArrayGetDim(safearray));                         \
      LONG array_lower_bound;                                            \
      EXPECT_HRESULT_SUCCEEDED(                                          \
          SafeArrayGetLBound(safearray, 1, &array_lower_bound));         \
      LONG array_upper_bound;                                            \
      EXPECT_HRESULT_SUCCEEDED(                                          \
          SafeArrayGetUBound(safearray, 1, &array_upper_bound));         \
      const size_t count = array_upper_bound - array_lower_bound + 1;    \
      EXPECT_EQ(expected_property_values.size(), count);                 \
      if (sizeof(T) == ::SafeArrayGetElemsize(safearray) &&              \
          count == expected_property_values.size()) {                    \
        T* array_data;                                                   \
        EXPECT_HRESULT_SUCCEEDED(::SafeArrayAccessData(                  \
            safearray, reinterpret_cast<void**>(&array_data)));          \
        for (size_t i = 0; i < count; ++i) {                             \
          EXPECT_EQ(array_data[i], expected_property_values[i]);         \
        }                                                                \
        EXPECT_HRESULT_SUCCEEDED(::SafeArrayUnaccessData(safearray));    \
      }                                                                  \
    }
  
  #define EXPECT_UIA_TEXTATTRIBUTE_EQ(provider, attribute, variant)          \
    {                                                                        \
      base::win::ScopedVariant scoped_variant;                               \
      EXPECT_HRESULT_SUCCEEDED(                                              \
          provider->GetAttributeValue(attribute, scoped_variant.Receive())); \
      EXPECT_EQ(0, scoped_variant.Compare(variant));                         \
    }
  
  #define EXPECT_UIA_TEXTATTRIBUTE_MIXED(provider, attribute)                \
    {                                                                        \
      ComPtr<IUnknown> expected_mixed;                                       \
      EXPECT_HRESULT_SUCCEEDED(                                              \
          ::UiaGetReservedMixedAttributeValue(&expected_mixed));             \
      base::win::ScopedVariant scoped_variant;                               \
      EXPECT_HRESULT_SUCCEEDED(                                              \
          provider->GetAttributeValue(attribute, scoped_variant.Receive())); \
      EXPECT_EQ(VT_UNKNOWN, scoped_variant.type());                          \
      EXPECT_EQ(expected_mixed.Get(), V_UNKNOWN(scoped_variant.ptr()));      \
    }
  
  #define EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(provider, attribute)           \
    {                                                                          \
      ComPtr<IUnknown> expected_notsupported;                                  \
      EXPECT_HRESULT_SUCCEEDED(                                                \
          ::UiaGetReservedNotSupportedValue(&expected_notsupported));          \
      base::win::ScopedVariant scoped_variant;                                 \
      EXPECT_HRESULT_SUCCEEDED(                                                \
          provider->GetAttributeValue(attribute, scoped_variant.Receive()));   \
      EXPECT_EQ(VT_UNKNOWN, scoped_variant.type());                            \
      EXPECT_EQ(expected_notsupported.Get(), V_UNKNOWN(scoped_variant.ptr())); \
    }
  
  #define EXPECT_UIA_TEXTRANGE_EQ(provider, expected_content) \
    {                                                         \
      base::win::ScopedBstr provider_content;                 \
      EXPECT_HRESULT_SUCCEEDED(                               \
          provider->GetText(-1, provider_content.Receive())); \
      EXPECT_STREQ(expected_content, provider_content.Get()); \
    }
  
  #define EXPECT_UIA_FIND_TEXT(text_range_provider, search_term, ignore_case)  \
    {                                                                          \
      base::win::ScopedBstr find_string(search_term);                          \
      ComPtr<ITextRangeProvider> text_range_provider_found;                    \
      EXPECT_HRESULT_SUCCEEDED(text_range_provider->FindText(                  \
          find_string.Get(), false, ignore_case, &text_range_provider_found)); \
      base::win::ScopedBstr found_content;                                     \
      EXPECT_HRESULT_SUCCEEDED(                                                \
          text_range_provider_found->GetText(-1, found_content.Receive()));    \
      if (ignore_case)                                                         \
        EXPECT_EQ(0, _wcsicmp(found_content.Get(), find_string.Get()));        \
      else                                                                     \
        EXPECT_EQ(0, wcscmp(found_content.Get(), find_string.Get()));          \
    }
  
  #define EXPECT_UIA_FIND_TEXT_NO_MATCH(text_range_provider, search_term,      \
                                        ignore_case)                           \
    {                                                                          \
      base::win::ScopedBstr find_string(search_term);                          \
      ComPtr<ITextRangeProvider> text_range_provider_found;                    \
      EXPECT_HRESULT_SUCCEEDED(text_range_provider->FindText(                  \
          find_string.Get(), false, ignore_case, &text_range_provider_found)); \
      EXPECT_EQ(nullptr, text_range_provider_found);                           \
    }
  
  #define EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider, endpoint, unit,  \
                                           count, expected_text, expected_count) \
    {                                                                            \
      int result_count;                                                          \
      EXPECT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(          \
          endpoint, unit, count, &result_count));                                \
      EXPECT_EQ(expected_count, result_count);                                   \
      EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, expected_text);               \
    }
  
  #define EXPECT_UIA_MOVE(text_range_provider, unit, count, expected_text, \
                          expected_count)                                  \
    {                                                                      \
      int result_count;                                                    \
      EXPECT_HRESULT_SUCCEEDED(                                            \
          text_range_provider->Move(unit, count, &result_count));          \
      EXPECT_EQ(expected_count, result_count);                             \
      EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, expected_text);         \
    }
  
  #define EXPECT_ENCLOSING_ELEMENT(ax_node_given, ax_node_expected)            \
    {                                                                          \
      ComPtr<ITextRangeProvider> text_range_provider;                          \
      GetTextRangeProviderFromTextNode(text_range_provider, ax_node_given);    \
      ComPtr<IRawElementProviderSimple> enclosing_element;                     \
      ASSERT_HRESULT_SUCCEEDED(                                                \
          text_range_provider->GetEnclosingElement(&enclosing_element));       \
      ComPtr<IRawElementProviderSimple> expected_text_provider =               \
          QueryInterfaceFromNode<IRawElementProviderSimple>(ax_node_expected); \
      EXPECT_EQ(expected_text_provider.Get(), enclosing_element.Get());        \
    }
  
  class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
   public:
    const AXNodePosition::AXPositionInstance& GetStart(
        const AXPlatformNodeTextRangeProviderWin* text_range) {
      return text_range->start_;
    }
  
    const AXNodePosition::AXPositionInstance& GetEnd(
        const AXPlatformNodeTextRangeProviderWin* text_range) {
      return text_range->end_;
    }
  
    ui::AXPlatformNodeWin* GetOwner(
        const AXPlatformNodeTextRangeProviderWin* text_range) {
      return text_range->owner_.Get();
    }
  
    void NormalizeTextRange(AXPlatformNodeTextRangeProviderWin* text_range) {
      text_range->NormalizeTextRange();
    }
  
    ComPtr<AXPlatformNodeTextRangeProviderWin> CloneTextRangeProviderWin(
        AXPlatformNodeTextRangeProviderWin* text_range) {
      ComPtr<ITextRangeProvider> clone;
      text_range->Clone(&clone);
      ComPtr<AXPlatformNodeTextRangeProviderWin> clone_win;
      clone->QueryInterface(IID_PPV_ARGS(&clone_win));
      return clone_win;
    }
  
    void GetTextRangeProviderFromTextNode(
        ComPtr<ITextRangeProvider>& text_range_provider,
        ui::AXNode* text_node) {
      ComPtr<IRawElementProviderSimple> provider_simple =
          QueryInterfaceFromNode<IRawElementProviderSimple>(text_node);
      ASSERT_NE(nullptr, provider_simple.Get());
  
      ComPtr<ITextProvider> text_provider;
      EXPECT_HRESULT_SUCCEEDED(
          provider_simple->GetPatternProvider(UIA_TextPatternId, &text_provider));
      ASSERT_NE(nullptr, text_provider.Get());
  
      EXPECT_HRESULT_SUCCEEDED(
          text_provider->get_DocumentRange(&text_range_provider));
      ASSERT_NE(nullptr, text_range_provider.Get());
    }
  
    void CreateTextRangeProviderWin(
        ComPtr<AXPlatformNodeTextRangeProviderWin>& text_range_provider_win,
        AXPlatformNodeWin* owner,
        AXTreeID tree_id,
        AXNode::AXID start_anchor_id,
        int start_offset,
        ax::mojom::TextAffinity start_affinity,
        AXNode::AXID end_anchor_id,
        int end_offset,
        ax::mojom::TextAffinity end_affinity) {
      AXNodePosition::AXPositionInstance range_start =
          AXNodePosition::CreateTextPosition(tree_id, start_anchor_id,
                                             start_offset, start_affinity);
      AXNodePosition::AXPositionInstance range_end =
          AXNodePosition::CreateTextPosition(tree_id, end_anchor_id, end_offset,
                                             end_affinity);
  
      ComPtr<ITextRangeProvider> text_range_provider =
          AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
              owner, std::move(range_start), std::move(range_end));
  
      text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range_provider_win));
    }
  
    void ComputeWordBoundariesOffsets(const std::string& text,
                                      std::vector<int>& word_start_offsets,
                                      std::vector<int>& word_end_offsets) {
      char previous_char = ' ';
      word_start_offsets = std::vector<int>();
      for (size_t i = 0; i < text.size(); ++i) {
        if (previous_char == ' ' && text[i] != ' ')
          word_start_offsets.push_back(i);
        previous_char = text[i];
      }
  
      previous_char = ' ';
      word_end_offsets = std::vector<int>();
      for (size_t i = text.size(); i > 0; --i) {
        if (previous_char == ' ' && text[i - 1] != ' ')
          word_end_offsets.push_back(i);
        previous_char = text[i - 1];
      }
      std::reverse(word_end_offsets.begin(), word_end_offsets.end());
    }
  
    ui::AXTreeUpdate BuildTextDocument(
        const std::vector<std::string>& text_nodes_content,
        bool build_word_boundaries_offsets = false) {
      int current_id = 0;
      ui::AXNodeData root_data;
      root_data.id = ++current_id;
      root_data.role = ax::mojom::Role::kRootWebArea;
  
      ui::AXTreeUpdate update;
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      update.has_tree_data = true;
  
      for (const std::string& text_content : text_nodes_content) {
        ui::AXNodeData text_data;
        text_data.id = ++current_id;
        text_data.role = ax::mojom::Role::kStaticText;
        text_data.SetName(text_content);
  
        if (build_word_boundaries_offsets) {
          std::vector<int> word_end_offsets;
          std::vector<int> word_start_offsets;
          ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                       word_end_offsets);
          text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                        word_start_offsets);
          text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                        word_end_offsets);
        }
  
        root_data.child_ids.push_back(text_data.id);
        update.nodes.push_back(text_data);
      }
  
      update.nodes.insert(update.nodes.begin(), root_data);
      update.root_id = root_data.id;
      return update;
    }
  
    ui::AXTreeUpdate BuildAXTreeForBoundingRectangles() {
      // AXTree content:
      // <button>Button</button><input type="checkbox">Line 1<br>Line 2
      ui::AXNodeData root;
      ui::AXNodeData button;
      ui::AXNodeData check_box;
      ui::AXNodeData text_field;
      ui::AXNodeData static_text1;
      ui::AXNodeData line_break;
      ui::AXNodeData static_text2;
      ui::AXNodeData inline_box1;
      ui::AXNodeData inline_box2;
  
      const int ROOT_ID = 1;
      const int BUTTON_ID = 2;
      const int CHECK_BOX_ID = 3;
      const int TEXT_FIELD_ID = 4;
      const int STATIC_TEXT1_ID = 5;
      const int INLINE_BOX1_ID = 6;
      const int LINE_BREAK_ID = 7;
      const int STATIC_TEXT2_ID = 8;
      const int INLINE_BOX2_ID = 9;
  
      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;
  
      std::string LINE_1_TEXT = "Line 1";
      std::string LINE_2_TEXT = "Line 2";
      std::string LINE_BREAK_TEXT = "\n";
      std::string ALL_TEXT = LINE_1_TEXT + LINE_BREAK_TEXT + LINE_2_TEXT;
      std::string BUTTON_TEXT = "Button";
      std::string CHECKBOX_TEXT = "Check box";
  
      root.role = ax::mojom::Role::kRootWebArea;
  
      button.role = ax::mojom::Role::kButton;
      button.SetHasPopup(ax::mojom::HasPopup::kMenu);
      button.SetName(BUTTON_TEXT);
      button.SetValue(BUTTON_TEXT);
      button.relative_bounds.bounds = gfx::RectF(20, 20, 200, 30);
      button.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
                             check_box.id);
      root.child_ids.push_back(button.id);
  
      check_box.role = ax::mojom::Role::kCheckBox;
      check_box.SetCheckedState(ax::mojom::CheckedState::kTrue);
      check_box.SetName(CHECKBOX_TEXT);
      check_box.relative_bounds.bounds = gfx::RectF(20, 50, 200, 30);
      check_box.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
                                button.id);
      root.child_ids.push_back(check_box.id);
  
      text_field.role = ax::mojom::Role::kTextField;
      text_field.AddState(ax::mojom::State::kEditable);
      text_field.SetValue(ALL_TEXT);
      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_TEXT);
      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_TEXT);
      inline_box1.relative_bounds.bounds = gfx::RectF(220, 20, 100, 30);
      std::vector<int32_t> character_offsets1;
      // The width of each character is 5px.
      character_offsets1.push_back(225);  // "L" {220, 20, 5x30}
      character_offsets1.push_back(230);  // "i" {225, 20, 5x30}
      character_offsets1.push_back(235);  // "n" {230, 20, 5x30}
      character_offsets1.push_back(240);  // "e" {235, 20, 5x30}
      character_offsets1.push_back(245);  // " " {240, 20, 5x30}
      character_offsets1.push_back(250);  // "1" {245, 20, 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_break.id);
  
      line_break.role = ax::mojom::Role::kLineBreak;
      line_break.AddState(ax::mojom::State::kEditable);
      line_break.SetName(LINE_BREAK_TEXT);
      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_TEXT);
      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_TEXT);
      inline_box2.relative_bounds.bounds = gfx::RectF(220, 50, 100, 30);
      std::vector<int32_t> character_offsets2;
      // The width of each character is 7 px.
      character_offsets2.push_back(227);  // "L" {220, 50, 7x30}
      character_offsets2.push_back(234);  // "i" {227, 50, 7x30}
      character_offsets2.push_back(241);  // "n" {234, 50, 7x30}
      character_offsets2.push_back(248);  // "e" {241, 50, 7x30}
      character_offsets2.push_back(255);  // " " {248, 50, 7x30}
      character_offsets2.push_back(262);  // "2" {255, 50, 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});
  
      AXTreeUpdate update;
      update.has_tree_data = true;
      update.root_id = ROOT_ID;
      update.nodes = {root,       button,       check_box,
                      text_field, static_text1, inline_box1,
                      line_break, static_text2, inline_box2};
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      return update;
    }
  
    const base::string16 tree_for_move_full_text =
        L"First line of text\nStandalone line\n"
        L"bold text\nParagraph 1\nParagraph 2";
  
    ui::AXTreeUpdate BuildAXTreeForMove() {
      ui::AXNodeData group1_data;
      group1_data.id = 2;
      group1_data.role = ax::mojom::Role::kGenericContainer;
      group1_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  
      ui::AXNodeData text_data;
      text_data.id = 3;
      text_data.role = ax::mojom::Role::kStaticText;
      std::string text_content = "First line of text";
      text_data.SetName(text_content);
      std::vector<int> word_end_offsets;
      std::vector<int> word_start_offsets;
      ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                   word_end_offsets);
      text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                    word_start_offsets);
      text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                    word_end_offsets);
      group1_data.child_ids = {3};
  
      ui::AXNodeData group2_data;
      group2_data.id = 4;
      group2_data.role = ax::mojom::Role::kGenericContainer;
  
      ui::AXNodeData line_break1_data;
      line_break1_data.id = 5;
      line_break1_data.role = ax::mojom::Role::kLineBreak;
      line_break1_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
      line_break1_data.SetName("\n");
  
      ui::AXNodeData standalone_text_data;
      standalone_text_data.id = 6;
      standalone_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "Standalone line";
      standalone_text_data.SetName(text_content);
      ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                   word_end_offsets);
      standalone_text_data.AddIntListAttribute(
          ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
      standalone_text_data.AddIntListAttribute(
          ax::mojom::IntListAttribute::kWordEnds, word_end_offsets);
  
      ui::AXNodeData line_break2_data;
      line_break2_data.id = 7;
      line_break2_data.role = ax::mojom::Role::kLineBreak;
      line_break2_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
      line_break2_data.SetName("\n");
  
      group2_data.child_ids = {5, 6, 7};
      standalone_text_data.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
                                           line_break2_data.id);
      line_break2_data.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
                                       standalone_text_data.id);
  
      ui::AXNodeData bold_text_data;
      bold_text_data.id = 8;
      bold_text_data.role = ax::mojom::Role::kStaticText;
      bold_text_data.AddIntAttribute(
          ax::mojom::IntAttribute::kTextStyle,
          static_cast<int32_t>(ax::mojom::TextStyle::kBold));
      text_content = "bold text";
      bold_text_data.SetName(text_content);
      ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                   word_end_offsets);
      bold_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                         word_start_offsets);
      bold_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                         word_end_offsets);
  
      ui::AXNodeData paragraph1_data;
      paragraph1_data.id = 9;
      paragraph1_data.role = ax::mojom::Role::kParagraph;
      paragraph1_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  
      ui::AXNodeData paragraph1_text_data;
      paragraph1_text_data.id = 10;
      paragraph1_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "Paragraph 1";
      paragraph1_text_data.SetName(text_content);
      ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                   word_end_offsets);
      paragraph1_text_data.AddIntListAttribute(
          ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
      paragraph1_text_data.AddIntListAttribute(
          ax::mojom::IntListAttribute::kWordEnds, word_end_offsets);
      paragraph1_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  
      ui::AXNodeData ignored_text_data;
      ignored_text_data.id = 11;
      ignored_text_data.role = ax::mojom::Role::kStaticText;
      ignored_text_data.AddState(ax::mojom::State::kIgnored);
      text_content = "ignored text";
      ignored_text_data.SetName(text_content);
  
      paragraph1_data.child_ids = {10, 11};
  
      ui::AXNodeData paragraph2_data;
      paragraph2_data.id = 12;
      paragraph2_data.role = ax::mojom::Role::kParagraph;
      paragraph2_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  
      ui::AXNodeData paragraph2_text_data;
      paragraph2_text_data.id = 13;
      paragraph2_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "Paragraph 2";
      paragraph2_text_data.SetName(text_content);
      ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                   word_end_offsets);
      paragraph2_text_data.AddIntListAttribute(
          ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
      paragraph2_text_data.AddIntListAttribute(
          ax::mojom::IntListAttribute::kWordEnds, word_end_offsets);
      paragraph1_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
      paragraph2_data.child_ids = {13};
  
      ui::AXNodeData root_data;
      root_data.id = 1;
      root_data.role = ax::mojom::Role::kRootWebArea;
      root_data.child_ids = {2, 4, 8, 9, 12};
  
      ui::AXTreeUpdate update;
      update.has_tree_data = true;
      update.root_id = root_data.id;
      update.nodes = {root_data,           group1_data,
                      text_data,           group2_data,
                      line_break1_data,    standalone_text_data,
                      line_break2_data,    bold_text_data,
                      paragraph1_data,     paragraph1_text_data,
                      ignored_text_data,   paragraph2_data,
                      paragraph2_text_data};
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      return update;
    }
  
    ui::AXTreeUpdate BuildAXTreeForMoveByFormat() {
      //                    1
      //                    |
      //    -------------------------------------
      //    |       |       |    |    |    |    |
      //    2       4       8   10   12   14   16
      //    |       |       |    |    |    |    |
      //    |   ---------   |    |    |    |    |
      //    |   |   |   |   |    |    |    |    |
      //    3   5   6   7   9   11   13   15   17
  
      ui::AXNodeData group1_data;
      group1_data.id = 2;
      group1_data.role = ax::mojom::Role::kGenericContainer;
      group1_data.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily,
                                     "test font");
  
      ui::AXNodeData text_data;
      text_data.id = 3;
      text_data.role = ax::mojom::Role::kStaticText;
      std::string text_content = "Text with formatting";
      text_data.SetName(text_content);
      group1_data.child_ids = {3};
  
      ui::AXNodeData group2_data;
      group2_data.id = 4;
      group2_data.role = ax::mojom::Role::kGenericContainer;
  
      ui::AXNodeData line_break1_data;
      line_break1_data.id = 5;
      line_break1_data.role = ax::mojom::Role::kLineBreak;
  
      ui::AXNodeData standalone_text_data;
      standalone_text_data.id = 6;
      standalone_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "Standalone line with no formatting";
      standalone_text_data.SetName(text_content);
  
      ui::AXNodeData line_break2_data;
      line_break2_data.id = 7;
      line_break2_data.role = ax::mojom::Role::kLineBreak;
  
      group2_data.child_ids = {5, 6, 7};
  
      ui::AXNodeData group3_data;
      group3_data.id = 8;
      group3_data.role = ax::mojom::Role::kGenericContainer;
      group3_data.AddIntAttribute(
          ax::mojom::IntAttribute::kTextStyle,
          static_cast<int32_t>(ax::mojom::TextStyle::kBold));
  
      ui::AXNodeData bold_text_data;
      bold_text_data.id = 9;
      bold_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "bold text";
      bold_text_data.SetName(text_content);
      group3_data.child_ids = {9};
  
      ui::AXNodeData paragraph1_data;
      paragraph1_data.id = 10;
      paragraph1_data.role = ax::mojom::Role::kParagraph;
      paragraph1_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 100);
  
      ui::AXNodeData paragraph1_text_data;
      paragraph1_text_data.id = 11;
      paragraph1_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "Paragraph 1";
      paragraph1_text_data.SetName(text_content);
      paragraph1_data.child_ids = {11};
  
      ui::AXNodeData paragraph2_data;
      paragraph2_data.id = 12;
      paragraph2_data.role = ax::mojom::Role::kParagraph;
      paragraph2_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize,
                                        1.0f);
  
      ui::AXNodeData paragraph2_text_data;
      paragraph2_text_data.id = 13;
      paragraph2_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "Paragraph 2";
      paragraph2_text_data.SetName(text_content);
      paragraph2_data.child_ids = {13};
  
      ui::AXNodeData paragraph3_data;
      paragraph3_data.id = 14;
      paragraph3_data.role = ax::mojom::Role::kParagraph;
      paragraph3_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize,
                                        1.0f);
  
      ui::AXNodeData paragraph3_text_data;
      paragraph3_text_data.id = 15;
      paragraph3_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "Paragraph 3";
      paragraph3_text_data.SetName(text_content);
      paragraph3_data.child_ids = {15};
  
      ui::AXNodeData paragraph4_data;
      paragraph4_data.id = 16;
      paragraph4_data.role = ax::mojom::Role::kParagraph;
      paragraph4_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize,
                                        2.0f);
  
      ui::AXNodeData paragraph4_text_data;
      paragraph4_text_data.id = 17;
      paragraph4_text_data.role = ax::mojom::Role::kStaticText;
      text_content = "Paragraph 4";
      paragraph4_text_data.SetName(text_content);
      paragraph4_data.child_ids = {17};
  
      ui::AXNodeData root_data;
      root_data.id = 1;
      root_data.role = ax::mojom::Role::kRootWebArea;
      root_data.child_ids = {2, 4, 8, 10, 12, 14, 16};
  
      ui::AXTreeUpdate update;
      update.has_tree_data = true;
      update.root_id = root_data.id;
      update.nodes = {root_data,
                      group1_data,
                      text_data,
                      group2_data,
                      group3_data,
                      line_break1_data,
                      standalone_text_data,
                      line_break2_data,
                      bold_text_data,
                      paragraph1_data,
                      paragraph1_text_data,
                      paragraph2_data,
                      paragraph2_text_data,
                      paragraph3_data,
                      paragraph3_text_data,
                      paragraph4_data,
                      paragraph4_text_data};
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      return update;
    }
  
    ui::AXTreeUpdate BuildAXTreeForMoveByPage() {
      ui::AXNodeData root_data;
      root_data.id = 1;
      root_data.role = ax::mojom::Role::kDocument;
  
      ui::AXNodeData page_1_data;
      page_1_data.id = 2;
      page_1_data.role = ax::mojom::Role::kRegion;
      page_1_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsPageBreakingObject, true);
  
      ui::AXNodeData page_1_text_data;
      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};
  
      ui::AXNodeData page_2_data;
      page_2_data.id = 4;
      page_2_data.role = ax::mojom::Role::kRegion;
      page_2_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsPageBreakingObject, true);
  
      ui::AXNodeData page_2_text_data;
      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};
  
      ui::AXNodeData page_3_data;
      page_3_data.id = 6;
      page_3_data.role = ax::mojom::Role::kRegion;
      page_3_data.AddBoolAttribute(
          ax::mojom::BoolAttribute::kIsPageBreakingObject, true);
  
      ui::AXNodeData page_3_text_data;
      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};
  
      ui::AXTreeUpdate update;
      update.has_tree_data = true;
      update.root_id = root_data.id;
      update.nodes = {root_data,       page_1_data,      page_1_text_data,
                      page_2_data,     page_2_text_data, page_3_data,
                      page_3_text_data};
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      return update;
    }
  };
  
  class MockAXPlatformNodeTextRangeProviderWin
      : public CComObjectRootEx<CComMultiThreadModel>,
        public ITextRangeProvider {
   public:
    BEGIN_COM_MAP(MockAXPlatformNodeTextRangeProviderWin)
    COM_INTERFACE_ENTRY(ITextRangeProvider)
    END_COM_MAP()
  
    MockAXPlatformNodeTextRangeProviderWin() {}
    ~MockAXPlatformNodeTextRangeProviderWin() {}
  
    static HRESULT CreateMockTextRangeProvider(ITextRangeProvider** provider) {
      CComObject<MockAXPlatformNodeTextRangeProviderWin>* text_range_provider =
          nullptr;
      HRESULT hr =
          CComObject<MockAXPlatformNodeTextRangeProviderWin>::CreateInstance(
              &text_range_provider);
      if (SUCCEEDED(hr)) {
        *provider = text_range_provider;
      }
  
      return hr;
    }
  
    //
    // ITextRangeProvider methods.
    //
    IFACEMETHODIMP Clone(ITextRangeProvider** clone) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP Compare(ITextRangeProvider* other, BOOL* result) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP CompareEndpoints(TextPatternRangeEndpoint this_endpoint,
                                    ITextRangeProvider* other,
                                    TextPatternRangeEndpoint other_endpoint,
                                    int* result) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP ExpandToEnclosingUnit(TextUnit unit) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP FindAttribute(TEXTATTRIBUTEID attribute_id,
                                 VARIANT val,
                                 BOOL backward,
                                 ITextRangeProvider** result) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP FindText(BSTR string,
                            BOOL backwards,
                            BOOL ignore_case,
                            ITextRangeProvider** result) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP GetAttributeValue(TEXTATTRIBUTEID attribute_id,
                                     VARIANT* value) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP GetBoundingRectangles(SAFEARRAY** rectangles) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP GetEnclosingElement(
        IRawElementProviderSimple** element) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP GetText(int max_count, BSTR* text) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP Move(TextUnit unit, int count, int* units_moved) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,
                                      TextUnit unit,
                                      int count,
                                      int* units_moved) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP MoveEndpointByRange(
        TextPatternRangeEndpoint this_endpoint,
        ITextRangeProvider* other,
        TextPatternRangeEndpoint other_endpoint) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP Select() override { return E_NOTIMPL; }
  
    IFACEMETHODIMP AddToSelection() override { return E_NOTIMPL; }
  
    IFACEMETHODIMP RemoveFromSelection() override { return E_NOTIMPL; }
  
    IFACEMETHODIMP ScrollIntoView(BOOL align_to_top) override {
      return E_NOTIMPL;
    }
  
    IFACEMETHODIMP GetChildren(SAFEARRAY** children) override {
      return E_NOTIMPL;
    }
  };
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderClone) {
    Init(BuildTextDocument({"some text"}));
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetRootAsAXNode()->children()[0]);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
  
    ComPtr<ITextRangeProvider> text_range_provider_clone;
    text_range_provider->Clone(&text_range_provider_clone);
  
    ComPtr<AXPlatformNodeTextRangeProviderWin> original_range;
    ComPtr<AXPlatformNodeTextRangeProviderWin> clone_range;
  
    text_range_provider->QueryInterface(IID_PPV_ARGS(&original_range));
    text_range_provider_clone->QueryInterface(IID_PPV_ARGS(&clone_range));
  
    EXPECT_EQ(*GetStart(original_range.Get()), *GetStart(clone_range.Get()));
    EXPECT_EQ(*GetEnd(original_range.Get()), *GetEnd(clone_range.Get()));
    EXPECT_EQ(GetOwner(original_range.Get()), GetOwner(clone_range.Get()));
  
    // Clear original text range provider.
    text_range_provider.Reset();
    EXPECT_EQ(nullptr, text_range_provider.Get());
  
    // Ensure the clone still works correctly.
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider_clone, L"some text");
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderCompareEndpoints) {
    Init(BuildTextDocument({"some text", "more text"}));
  
    AXNode* root_node = GetRootAsAXNode();
  
    // Get the textRangeProvider for the document,
    // which contains text "some textmore text".
    ComPtr<ITextRangeProvider> document_text_range_provider;
    GetTextRangeProviderFromTextNode(document_text_range_provider, root_node);
  
    // Get the textRangeProvider for "some text".
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     root_node->children()[0]);
  
    // Get the textRangeProvider for "more text".
    ComPtr<ITextRangeProvider> more_text_range_provider;
    GetTextRangeProviderFromTextNode(more_text_range_provider,
                                     root_node->children()[1]);
  
    // Compare the endpoints of the document which contains "some textmore text".
    int result;
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_Start, &result));
    EXPECT_EQ(0, result);
  
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_End, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_End, &result));
    EXPECT_EQ(0, result);
  
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_End, &result));
    EXPECT_EQ(-1, result);
  
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_End, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_Start, &result));
    EXPECT_EQ(1, result);
  
    // Compare the endpoints of "some text" and "more text". The position at the
    // end of "some text" is logically equivalent to the position at the start of
    // "more text".
    EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_Start, more_text_range_provider.Get(),
        TextPatternRangeEndpoint_Start, &result));
    EXPECT_EQ(-1, result);
  
    EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_End, more_text_range_provider.Get(),
        TextPatternRangeEndpoint_Start, &result));
    EXPECT_EQ(0, result);
  
    // Compare the endpoints of "some text" with those of the entire document. The
    // position at the start of "some text" is logically equivalent to the
    // position at the start of the document.
    EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_Start, &result));
    EXPECT_EQ(0, result);
  
    EXPECT_HRESULT_SUCCEEDED(text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_End, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_End, &result));
    EXPECT_EQ(-1, result);
  
    // Compare the endpoints of "more text" with those of the entire document.
    EXPECT_HRESULT_SUCCEEDED(more_text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_Start, &result));
    EXPECT_EQ(1, result);
  
    EXPECT_HRESULT_SUCCEEDED(more_text_range_provider->CompareEndpoints(
        TextPatternRangeEndpoint_End, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_End, &result));
    EXPECT_EQ(0, result);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderExpandToEnclosingCharacter) {
    ui::AXTreeUpdate update = BuildTextDocument({"some text", "more text"});
    Init(update);
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"s");
  
    int count;
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 2, &count));
    ASSERT_EQ(2, count);
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 1, &count));
    ASSERT_EQ(1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"om");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"o");
  
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 9, &count));
    ASSERT_EQ(9, count);
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 8, &count));
    ASSERT_EQ(8, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"mo");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"m");
  
    // Move the start and end to the end of the document.
    // Expand to enclosing unit should never return a null position.
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 9, &count));
    ASSERT_EQ(8, count);
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 9, &count));
    ASSERT_EQ(9, count);
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"t");
  
    // Move both endpoints to the position before the start of the "more text"
    // anchor. Then, force the start to be on the position after the end of
    // "some text" by moving one character backward and one forward.
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -9, &count));
    ASSERT_EQ(-9, count);
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ -1,
        &count));
    ASSERT_EQ(-1, count);
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 1, &count));
    ASSERT_EQ(1, count);
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"m");
  
    // Check that the enclosing element of the range matches ATs expectations.
    ComPtr<IRawElementProviderSimple> more_text_provider =
        QueryInterfaceFromNode<IRawElementProviderSimple>(
            root_node->children()[1]);
    ComPtr<IRawElementProviderSimple> enclosing_element;
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->GetEnclosingElement(&enclosing_element));
    EXPECT_EQ(more_text_provider.Get(), enclosing_element.Get());
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderExpandToEnclosingWord) {
    Init(BuildTextDocument({"some text", "definitely not text"},
                           /*build_word_boundaries_offsets*/ true));
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetRootAsAXNode()->children()[1]);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"definitely not text");
  
    // Start endpoint is already on a word's start boundary.
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Word));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"definitely ");
  
    // Start endpoint is between a word's start and end boundaries.
    int count;
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ -2,
        &count));
    ASSERT_EQ(-2, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"xtdefinitely ");
  
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 4, &count));
    ASSERT_EQ(4, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"xtdefinitely not ");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Word));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"text");
  
    // Start endpoint is on a word's end boundary.
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 18,
        &count));
    ASSERT_EQ(18, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
  
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 1, &count));
    ASSERT_EQ(1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L" ");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Word));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"not ");
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderExpandToEnclosingLine) {
    Init(BuildTextDocument({"line #1", "maybe line #1?", "not line #1"}));
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetRootAsAXNode()->children()[0]);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"line #1");
  
    // Start endpoint is already on a line's start boundary.
    int count;
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -11, &count));
    ASSERT_EQ(-7, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Line));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"line #1");
  
    // Start endpoint is between a line's start and end boundaries.
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 13,
        &count));
    ASSERT_EQ(13, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
  
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 4, &count));
    ASSERT_EQ(4, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"line");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Line));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"maybe line #1?");
  
    // Start endpoint is on a line's end boundary.
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 29,
        &count));
    ASSERT_EQ(25, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Line));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"not line #1");
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderExpandToEnclosingParagraph) {
    Init(BuildAXTreeForMove());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
                            /*expected_text*/ tree_for_move_full_text.data());
  
    // Start endpoint is already on a paragraph's start boundary.
    int count;
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Paragraph, /*count*/ -6, &count));
    EXPECT_EQ(-5, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"First line of text\n");
  
    // Moving the start by two lines will create a degenerate range positioned
    // at the next paragraph (skipping the newline).
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Line, /*count*/ 2, &count));
    EXPECT_EQ(2, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Standalone line\n");
  
    // Move to the next paragraph via MoveEndpointByUnit (line), then move to
    // the middle of the paragraph via Move (word), then expand by paragraph.
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Line, /*count*/ 1, &count));
    EXPECT_EQ(1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 1,
                    /*expected_text*/
                    L"",
                    /*expected_count*/ 1);
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"bold text");
  
    // Create a degenerate range at the end of the document, then expand by
    // paragraph.
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Document, /*count*/ 1, &count));
    EXPECT_EQ(1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Paragraph 2");
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderExpandToEnclosingFormat) {
    Init(BuildAXTreeForMoveByFormat());
    AXNode* root_node = GetRootAsAXNode();
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    EXPECT_UIA_TEXTRANGE_EQ(
        text_range_provider,
        L"Text with formattingStandalone line with no formattingbold "
        L"textParagraph 1Paragraph 2Paragraph 3Paragraph 4");
  
    // https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationtextrange-expandtoenclosingunit
    // Consider two consecutive text units A and B.
    // The documentation illustrates 9 cases, but cases 1 and 9 are equivalent.
    // In each case, the expected output is a range from start of A to end of A.
  
    // Create a range encompassing nodes 11-15 which will serve as text units A
    // and B for this test.
    ComPtr<ITextRangeProvider> units_a_b_provider;
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->Clone(&units_a_b_provider));
    int count;
    ASSERT_HRESULT_SUCCEEDED(units_a_b_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 63,
        &count));
    ASSERT_EQ(63, count);
    ASSERT_HRESULT_SUCCEEDED(units_a_b_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -11, &count));
    ASSERT_EQ(-11, count);
    EXPECT_UIA_TEXTRANGE_EQ(units_a_b_provider,
                            L"Paragraph 1Paragraph 2Paragraph 3");
  
    // Create a range encompassing node 11 which will serve as our expected
    // value of a range from start of A to end of A.
    ComPtr<ITextRangeProvider> unit_a_provider;
    ASSERT_HRESULT_SUCCEEDED(units_a_b_provider->Clone(&unit_a_provider));
    ASSERT_HRESULT_SUCCEEDED(unit_a_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -22, &count));
    ASSERT_EQ(-22, count);
    EXPECT_UIA_TEXTRANGE_EQ(unit_a_provider, L"Paragraph 1");
  
    // Case 1: Degenerate range at start of A.
    {
      SCOPED_TRACE("Case 1: Degenerate range at start of A.");
      ComPtr<ITextRangeProvider> test_case_provider;
      ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByRange(
          TextPatternRangeEndpoint_End, test_case_provider.Get(),
          TextPatternRangeEndpoint_Start));
      EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"");
  
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
      BOOL are_same;
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->Compare(unit_a_provider.Get(), &are_same));
      EXPECT_TRUE(are_same);
    }
  
    // Case 2: Range from start of A to middle of A.
    {
      SCOPED_TRACE("Case 2: Range from start of A to middle of A.");
      ComPtr<ITextRangeProvider> test_case_provider;
      ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
          TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -7,
          &count));
      ASSERT_EQ(-7, count);
      EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"Para");
  
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
      BOOL are_same;
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->Compare(unit_a_provider.Get(), &are_same));
      EXPECT_TRUE(are_same);
    }
  
    // Case 3: Range from start of A to end of A.
    {
      SCOPED_TRACE("Case 3: Range from start of A to end of A.");
      ComPtr<ITextRangeProvider> test_case_provider;
      ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
      EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"Paragraph 1");
  
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
      BOOL are_same;
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->Compare(unit_a_provider.Get(), &are_same));
      EXPECT_TRUE(are_same);
    }
  
    // Case 4: Range from start of A to middle of B.
    {
      SCOPED_TRACE("Case 4: Range from start of A to middle of B.");
      ComPtr<ITextRangeProvider> test_case_provider;
      ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
          TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 4, &count));
      ASSERT_EQ(4, count);
      EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"Paragraph 1Para");
  
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
      BOOL are_same;
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->Compare(unit_a_provider.Get(), &are_same));
      EXPECT_TRUE(are_same);
    }
  
    // Case 5: Degenerate range in middle of A.
    {
      SCOPED_TRACE("Case 5: Degenerate range in middle of A.");
      ComPtr<ITextRangeProvider> test_case_provider;
      ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
          TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 4,
          &count));
      ASSERT_EQ(4, count);
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByRange(
          TextPatternRangeEndpoint_End, test_case_provider.Get(),
          TextPatternRangeEndpoint_Start));
      EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"");
  
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
      BOOL are_same;
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->Compare(unit_a_provider.Get(), &are_same));
      EXPECT_TRUE(are_same);
    }
  
    // Case 6: Range from middle of A to middle of A.
    {
      SCOPED_TRACE("Case 6: Range from middle of A to middle of A.");
      ComPtr<ITextRangeProvider> test_case_provider;
      ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
          TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 4,
          &count));
      ASSERT_EQ(4, count);
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
          TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -2,
          &count));
      ASSERT_EQ(-2, count);
      EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"graph");
  
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
      BOOL are_same;
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->Compare(unit_a_provider.Get(), &are_same));
      EXPECT_TRUE(are_same);
    }
  
    // Case 7: Range from middle of A to end of A.
    {
      SCOPED_TRACE("Case 7: Range from middle of A to end of A.");
      ComPtr<ITextRangeProvider> test_case_provider;
      ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
          TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 4,
          &count));
      ASSERT_EQ(4, count);
      EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"graph 1");
  
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
      BOOL are_same;
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->Compare(unit_a_provider.Get(), &are_same));
      EXPECT_TRUE(are_same);
    }
  
    // Case 8: Range from middle of A to middle of B.
    {
      SCOPED_TRACE("Case 8: Range from middle of A to middle of B.");
      ComPtr<ITextRangeProvider> test_case_provider;
      ASSERT_HRESULT_SUCCEEDED(unit_a_provider->Clone(&test_case_provider));
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
          TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 4,
          &count));
      ASSERT_EQ(4, count);
      ASSERT_HRESULT_SUCCEEDED(test_case_provider->MoveEndpointByUnit(
          TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 4, &count));
      ASSERT_EQ(4, count);
      EXPECT_UIA_TEXTRANGE_EQ(test_case_provider, L"graph 1Para");
  
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->ExpandToEnclosingUnit(TextUnit_Format));
      BOOL are_same;
      ASSERT_HRESULT_SUCCEEDED(
          test_case_provider->Compare(unit_a_provider.Get(), &are_same));
      EXPECT_TRUE(are_same);
    }
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderExpandToEnclosingFormatWithEmptyObjects) {
    // This test updates the tree structure to test a specific edge case.
    //
    // When using heading navigation, the empty objects (see
    // AXPosition::IsEmptyObjectReplacedByCharacter for information about empty
    // objects) sometimes cause a problem with
    // AXPlatformNodeTextRangeProviderWin::ExpandToEnlosingUnit.
    // With some specific AXTree (like the one used below), the empty object
    // causes ExpandToEnclosingUnit to move the range back on the heading that it
    // previously was instead of moving it forward/backward to the next heading.
    // To avoid this, empty objects are always marked as format boundaries.
    //
    // The issue normally occurs when a heading is directly followed by an ignored
    // empty object, itself followed by an unignored empty object.
    //
    // ++1 kRootWebArea
    // ++++2 kHeading
    // ++++++3 kStaticText
    // ++++++++4 kInlineTextBox
    // ++++5 kGenericContainer ignored
    // ++++6 kGenericContainer
    ui::AXNodeData root_1;
    ui::AXNodeData heading_2;
    ui::AXNodeData static_text_3;
    ui::AXNodeData inline_box_4;
    ui::AXNodeData generic_container_5;
    ui::AXNodeData generic_container_6;
  
    root_1.id = 1;
    heading_2.id = 2;
    static_text_3.id = 3;
    inline_box_4.id = 4;
    generic_container_5.id = 5;
    generic_container_6.id = 6;
  
    root_1.role = ax::mojom::Role::kRootWebArea;
    root_1.child_ids = {heading_2.id, generic_container_5.id,
                        generic_container_6.id};
  
    heading_2.role = ax::mojom::Role::kHeading;
    heading_2.child_ids = {static_text_3.id};
  
    static_text_3.role = ax::mojom::Role::kStaticText;
    static_text_3.child_ids = {inline_box_4.id};
    static_text_3.SetName("3.14");
  
    inline_box_4.role = ax::mojom::Role::kInlineTextBox;
    inline_box_4.SetName("3.14");
  
    generic_container_5.role = ax::mojom::Role::kGenericContainer;
    generic_container_5.AddBoolAttribute(
        ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
    generic_container_5.AddState(ax::mojom::State::kIgnored);
  
    generic_container_6.role = ax::mojom::Role::kGenericContainer;
    generic_container_6.AddBoolAttribute(
        ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  
    ui::AXTreeUpdate update;
    ui::AXTreeData tree_data;
    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.tree_data = tree_data;
    update.has_tree_data = true;
    update.root_id = root_1.id;
    update.nodes.push_back(root_1);
    update.nodes.push_back(heading_2);
    update.nodes.push_back(static_text_3);
    update.nodes.push_back(inline_box_4);
    update.nodes.push_back(generic_container_5);
    update.nodes.push_back(generic_container_6);
  
    Init(update);
  
    AXNode* root_node = GetRootAsAXNode();
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"3.14\n\xFFFC");
  
    // Create a degenerate range positioned at the boundary between nodes 4 and 6,
    // e.g., "3.14<>" and "<\xFFFC>" (because node 5 is ignored).
    int count;
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_Start, TextUnit_Character, /*count*/ 5, &count));
    ASSERT_EQ(5, count);
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -1, &count));
    ASSERT_EQ(-1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
  
    // ExpandToEnclosingUnit should move the range to the next non-ignored empty
    // object (i.e, node 6), and not at the beginning of node 4.
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Format));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"\xFFFC");
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderExpandToEnclosingDocument) {
    Init(BuildTextDocument({"some text", "more text", "even more text"}));
  
    AXNode* root_node = GetRootAsAXNode();
    AXNode* text_node = root_node->children()[0];
    AXNode* more_text_node = root_node->children()[1];
    AXNode* even_more_text_node = root_node->children()[2];
  
    // Run the test twice, one for TextUnit_Document and once for TextUnit_Page,
    // since they should have identical behavior.
    const TextUnit textunit_types[] = {TextUnit_Document, TextUnit_Page};
    ComPtr<ITextRangeProvider> text_range_provider;
  
    for (auto& textunit : textunit_types) {
      GetTextRangeProviderFromTextNode(text_range_provider, text_node);
      ASSERT_HRESULT_SUCCEEDED(
          text_range_provider->ExpandToEnclosingUnit(textunit));
      EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
                              L"some textmore texteven more text");
  
      GetTextRangeProviderFromTextNode(text_range_provider, more_text_node);
      ASSERT_HRESULT_SUCCEEDED(
          text_range_provider->ExpandToEnclosingUnit(textunit));
      EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
                              L"some textmore texteven more text");
  
      GetTextRangeProviderFromTextNode(text_range_provider, even_more_text_node);
      ASSERT_HRESULT_SUCCEEDED(
          text_range_provider->ExpandToEnclosingUnit(textunit));
      EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
                              L"some textmore texteven more text");
    }
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderInvalidCalls) {
    // Test for when a text range provider is invalid. Because no ax tree is
    // available, the anchor is invalid, so the text range provider fails the
    // validate call.
    {
      Init(BuildTextDocument({}));
  
      ComPtr<ITextRangeProvider> text_range_provider;
      GetTextRangeProviderFromTextNode(text_range_provider, GetRootAsAXNode());
  
      DestroyTree();
      ComPtr<ITextRangeProvider> text_range_provider_clone;
      EXPECT_UIA_ELEMENTNOTAVAILABLE(
          text_range_provider->Clone(&text_range_provider_clone));
  
      BOOL compare_result;
      EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->Compare(
          text_range_provider.Get(), &compare_result));
  
      int compare_endpoints_result;
      EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->CompareEndpoints(
          TextPatternRangeEndpoint_Start, text_range_provider.Get(),
          TextPatternRangeEndpoint_Start, &compare_endpoints_result));
  
      VARIANT attr_val;
      V_VT(&attr_val) = VT_BOOL;
      V_BOOL(&attr_val) = VARIANT_TRUE;
      ComPtr<ITextRangeProvider> matched_range_provider;
      EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, attr_val, true, &matched_range_provider));
  
      EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->MoveEndpointByRange(
          TextPatternRangeEndpoint_Start, text_range_provider.Get(),
          TextPatternRangeEndpoint_Start));
  
      EXPECT_UIA_ELEMENTNOTAVAILABLE(text_range_provider->Select());
    }
  
    // Test for when this provider is valid, but the other provider is not an
    // instance of AXPlatformNodeTextRangeProviderWin, so no operation can be
    // performed on the other provider.
    {
      Init(BuildTextDocument({}));
  
      ComPtr<ITextRangeProvider> this_provider;
      GetTextRangeProviderFromTextNode(this_provider, GetRootAsAXNode());
  
      ComPtr<ITextRangeProvider> other_provider_different_type;
      MockAXPlatformNodeTextRangeProviderWin::CreateMockTextRangeProvider(
          &other_provider_different_type);
  
      BOOL compare_result;
      EXPECT_UIA_INVALIDOPERATION(this_provider->Compare(
          other_provider_different_type.Get(), &compare_result));
  
      int compare_endpoints_result;
      EXPECT_UIA_INVALIDOPERATION(this_provider->CompareEndpoints(
          TextPatternRangeEndpoint_Start, other_provider_different_type.Get(),
          TextPatternRangeEndpoint_Start, &compare_endpoints_result));
  
      EXPECT_UIA_INVALIDOPERATION(this_provider->MoveEndpointByRange(
          TextPatternRangeEndpoint_Start, other_provider_different_type.Get(),
          TextPatternRangeEndpoint_Start));
    }
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetText) {
    Init(BuildTextDocument({"some text", "more text"}));
  
    AXNode* root_node = GetRootAsAXNode();
    AXNode* text_node = root_node->children()[0];
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, text_node);
  
    base::win::ScopedBstr text_content;
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetText(-1, text_content.Receive()));
    EXPECT_STREQ(text_content.Get(), L"some text");
    text_content.Reset();
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetText(4, text_content.Receive()));
    EXPECT_STREQ(text_content.Get(), L"some");
    text_content.Reset();
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetText(0, text_content.Receive()));
    EXPECT_STREQ(text_content.Get(), L"");
    text_content.Reset();
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetText(9, text_content.Receive()));
    EXPECT_STREQ(text_content.Get(), L"some text");
    text_content.Reset();
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetText(10, text_content.Receive()));
    EXPECT_STREQ(text_content.Get(), L"some text");
    text_content.Reset();
  
    EXPECT_HRESULT_FAILED(text_range_provider->GetText(-1, nullptr));
  
    EXPECT_HRESULT_FAILED(
        text_range_provider->GetText(-2, text_content.Receive()));
    text_content.Reset();
  
    ComPtr<ITextRangeProvider> document_textrange;
    GetTextRangeProviderFromTextNode(document_textrange, root_node);
  
    EXPECT_HRESULT_SUCCEEDED(
        document_textrange->GetText(-1, text_content.Receive()));
    EXPECT_STREQ(text_content.Get(), L"some textmore text");
    text_content.Reset();
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveCharacter) {
    Init(BuildAXTreeForMove());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    // Moving by 0 should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, /*count*/ 0,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ 0);
  
    // Move forward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ 1,
                    /*expected_text*/ L"i",
                    /*expected_count*/ 1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ 18,
                    /*expected_text*/ L"S",
                    /*expected_count*/ 18);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ 16,
                    /*expected_text*/ L"b",
                    /*expected_count*/ 16);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ 60,
                    /*expected_text*/ L"2",
                    /*expected_count*/ 30);
  
    // Trying to move past the last character should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ 1,
                    /*expected_text*/ L"2",
                    /*expected_count*/ 0);
  
    // Move backward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ -2,
                    /*expected_text*/ L"h",
                    /*expected_count*/ -2);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ -9,
                    /*expected_text*/ L"1",
                    /*expected_count*/ -9);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ -60,
                    /*expected_text*/ L"F",
                    /*expected_count*/ -54);
  
    // Moving backward by any number of characters at the start of document
    // should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ -1,
                    /*expected_text*/
                    L"F",
                    /*expected_count*/ 0);
  
    // Degenerate range moves.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ -1,
        /*expected_text*/ L"",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ 4,
                    /*expected_text*/ L"",
                    /*expected_count*/ 4);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 62);
  
    // Trying to move past the last character should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 0);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                    /*count*/ -2,
                    /*expected_text*/ L"",
                    /*expected_count*/ -2);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveFormat) {
    Init(BuildAXTreeForMoveByFormat());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    // Moving by 0 should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 0,
                    /*expected_text*/
                    L"Text with formattingStandalone line with no formattingbold "
                    L"textParagraph 1Paragraph 2Paragraph 3Paragraph 4",
                    /*expected_count*/ 0);
  
    // Move forward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 1,
                    /*expected_text*/ L"Standalone line with no formatting",
                    /*expected_count*/ 1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 2,
                    /*expected_text*/ L"Paragraph 1",
                    /*expected_count*/ 2);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 1,
                    /*expected_text*/ L"Paragraph 2Paragraph 3",
                    /*expected_count*/ 1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 1,
                    /*expected_text*/ L"Paragraph 4",
                    /*expected_count*/ 1);
  
    // Trying to move past the last format should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 1,
                    /*expected_text*/ L"Paragraph 4",
                    /*expected_count*/ 0);
  
    // Move backward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ -3,
                    /*expected_text*/ L"bold text",
                    /*expected_count*/ -3);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ -1,
                    /*expected_text*/ L"Standalone line with no formatting",
                    /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ -1,
                    /*expected_text*/ L"Text with formatting",
                    /*expected_count*/ -1);
  
    // Moving backward by any number of formats at the start of document
    // should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ -1,
                    /*expected_text*/
                    L"Text with formatting",
                    /*expected_count*/ 0);
  
    // Test degenerate range creation at the beginning of the document.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ -1,
        /*expected_text*/ L"",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ 1,
        /*expected_text*/ L"Text with formatting",
        /*expected_count*/ 1);
  
    // Test degenerate range creation at the end of the document.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 5,
                    /*expected_text*/ L"Paragraph 4",
                    /*expected_count*/ 5);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
        /*count*/ 1,
        /*expected_text*/ L"",
        /*expected_count*/ 1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
        /*count*/ -1,
        /*expected_text*/ L"Paragraph 4",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
        /*count*/ 1,
        /*expected_text*/ L"",
        /*expected_count*/ 1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Format,
        /*count*/ -1,
        /*expected_text*/ L"Paragraph 4",
        /*expected_count*/ -1);
  
    // Degenerate range moves.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ -5,
                    /*expected_text*/ L"Text with formatting",
                    /*expected_count*/ -5);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ -1,
        /*expected_text*/ L"",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 3,
                    /*expected_text*/ L"",
                    /*expected_count*/ 3);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 3);
  
    // Trying to move past the last format should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 0);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Format,
                    /*count*/ -2,
                    /*expected_text*/ L"",
                    /*expected_count*/ -2);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMovePage) {
    Init(BuildAXTreeForMoveByPage());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    // Moving by 0 should have no effect.
    EXPECT_UIA_MOVE(
        text_range_provider, TextUnit_Page,
        /*count*/ 0,
        /*expected_text*/
        L"some text on page 1\nsome text on page 2some more text on page 3",
        /*expected_count*/ 0);
  
    // Backwards endpoint moves
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Page,
        /*count*/ -1,
        /*expected_text*/ L"some text on page 1\nsome text on page 2",
        /*expected_count*/ -1);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Page,
                                     /*count*/ -5,
                                     /*expected_text*/ L"",
                                     /*expected_count*/ -2);
  
    // Forwards endpoint move
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Page,
        /*count*/ 5,
        /*expected_text*/
        L"some text on page 1\nsome text on page 2some more text on page 3",
        /*expected_count*/ 3);
  
    // Range moves
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
                    /*count*/ 1,
                    /*expected_text*/ L"some text on page 2",
                    /*expected_count*/ 1);
  
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
                    /*count*/ 1,
                    /*expected_text*/ L"some more text on page 3",
                    /*expected_count*/ 1);
  
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
                    /*count*/ -1,
                    /*expected_text*/ L"some text on page 2",
                    /*expected_count*/ -1);
  
    // ExpandToEnclosingUnit - first move by character so it's not on a
    // page boundary before calling ExpandToEnclosingUnit
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ -2,
        /*expected_text*/ L"some text on page",
        /*expected_count*/ -2);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 2,
        /*expected_text*/ L"me text on page",
        /*expected_count*/ 2);
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Page));
  
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
                    /*count*/ 0,
                    /*expected_text*/
                    L"some text on page 2",
                    /*expected_count*/ 0);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveWord) {
    Init(BuildAXTreeForMove());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    // Moving by 0 should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word, /*count*/ 0,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ 0);
  
    // Move forward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 1,
                    /*expected_text*/ L"line ",
                    /*expected_count*/ 1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 2,
                    /*expected_text*/ L"text",
                    /*expected_count*/ 2);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 2,
                    /*expected_text*/ L"line",
                    /*expected_count*/ 2);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 3,
                    /*expected_text*/ L"Paragraph ",
                    /*expected_count*/ 3);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 6,
                    /*expected_text*/ L"2",
                    /*expected_count*/ 3);
  
    // Trying to move past the last word should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 1,
                    /*expected_text*/ L"2",
                    /*expected_count*/ 0);
  
    // Move backward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ -3,
                    /*expected_text*/ L"Paragraph ",
                    /*expected_count*/ -3);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ -3,
                    /*expected_text*/ L"line",
                    /*expected_count*/ -3);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ -2,
                    /*expected_text*/ L"text",
                    /*expected_count*/ -2);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ -6,
                    /*expected_text*/ L"First ",
                    /*expected_count*/ -3);
  
    // Moving backward by any number of words at the start of document
    // should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ -20,
                    /*expected_text*/ L"First ",
                    /*expected_count*/ 0);
  
    // Degenerate range moves.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ -1,
                                     /*expected_text*/ L"",
                                     /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 4,
                    /*expected_text*/ L"",
                    /*expected_count*/ 4);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 8);
  
    // Trying to move past the last word should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 0);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                    /*count*/ -2,
                    /*expected_text*/ L"",
                    /*expected_count*/ -2);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveLine) {
    Init(BuildAXTreeForMove());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    // Moving by 0 should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line, /*count*/ 0,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ 0);
  
    // Move forward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ 2,
                    /*expected_text*/ L"Standalone line",
                    /*expected_count*/ 2);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ 1,
                    /*expected_text*/ L"bold text",
                    /*expected_count*/ 1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ 10,
                    /*expected_text*/ L"Paragraph 2",
                    /*expected_count*/ 2);
  
    // Trying to move past the last line should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ 1,
                    /*expected_text*/ L"Paragraph 2",
                    /*expected_count*/ 0);
  
    // Move backward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ -1,
                    /*expected_text*/ L"Paragraph 1",
                    /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ -5,
                    /*expected_text*/ L"First line of text",
                    /*expected_count*/ -4);
  
    // Moving backward by any number of lines at the start of document
    // should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ -20,
                    /*expected_text*/ L"First line of text",
                    /*expected_count*/ 0);
  
    // Degenerate range moves.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Line,
                                     /*count*/ -1,
                                     /*expected_text*/ L"",
                                     /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ 4,
                    /*expected_text*/ L"",
                    /*expected_count*/ 4);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 2);
  
    // Trying to move past the last line should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 0);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Line,
                    /*count*/ -2,
                    /*expected_text*/ L"",
                    /*expected_count*/ -2);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveParagraph) {
    Init(BuildAXTreeForMove());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    // Moving by 0 should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph, /*count*/ 0,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ 0);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
        /*count*/ -4,
        /*expected_text*/ L"First line of text\n",
        /*expected_count*/ -4);
  
    // Move forward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 1,
                    /*expected_text*/ L"Standalone line\n",
                    /*expected_count*/ 1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 1,
                    /*expected_text*/ L"bold text",
                    /*expected_count*/ 1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 1,
                    /*expected_text*/ L"Paragraph 1",
                    /*expected_count*/ 1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 2,
                    /*expected_text*/ L"Paragraph 2",
                    /*expected_count*/ 1);
  
    // Trying to move past the last paragraph should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 1,
                    /*expected_text*/ L"Paragraph 2",
                    /*expected_count*/ 0);
  
    // Move backward.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ -3,
                    /*expected_text*/ L"Standalone line\n",
                    /*expected_count*/ -3);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ -1,
                    /*expected_text*/ L"First line of text\n",
                    /*expected_count*/ -1);
  
    // Moving backward by any number of paragraphs at the start of document
    // should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ -1,
                    /*expected_text*/
                    L"First line of text\n",
                    /*expected_count*/ 0);
  
    // Test degenerate range creation at the beginning of the document.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
        /*count*/ -1,
        /*expected_text*/ L"",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
        /*count*/ 1,
        /*expected_text*/ L"First line of text\n",
        /*expected_count*/ 1);
  
    // Test degenerate range creation at the end of the document.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 6,
                    /*expected_text*/ L"Paragraph 2",
                    /*expected_count*/ 4);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
        /*count*/ 1,
        /*expected_text*/ L"",
        /*expected_count*/ 1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
        /*count*/ -1,
        /*expected_text*/ L"Paragraph 2",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
        /*count*/ 1,
        /*expected_text*/ L"",
        /*expected_count*/ 1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
        /*count*/ -1,
        /*expected_text*/ L"Paragraph 2",
        /*expected_count*/ -1);
  
    // Degenerate range moves.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ -7,
                    /*expected_text*/ L"First line of text\n",
                    /*expected_count*/ -4);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
        /*count*/ -1,
        /*expected_text*/ L"",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 3,
                    /*expected_text*/ L"",
                    /*expected_count*/ 3);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 2);
  
    // Trying to move past the last paragraph should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ 70,
                    /*expected_text*/ L"",
                    /*expected_count*/ 0);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                    /*count*/ -2,
                    /*expected_text*/ L"",
                    /*expected_count*/ -2);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveDocument) {
    Init(BuildAXTreeForMove());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    // Moving by 0 should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document, /*count*/ 0,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ 0);
  
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document, /*count*/ -1,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document, /*count*/ 2,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ 0);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page, /*count*/ 1,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ 0);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page, /*count*/ -1,
                    /*expected_text*/ tree_for_move_full_text.data(),
                    /*expected_count*/ 0);
  
    // Degenerate range moves.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Document,
        /*count*/ -2,
        /*expected_text*/ L"",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
                    /*count*/ 4,
                    /*expected_text*/ L"",
                    /*expected_count*/ 1);
  
    // Trying to move past the last character should have no effect.
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document,
                    /*count*/ 1,
                    /*expected_text*/ L"",
                    /*expected_count*/ 0);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
                    /*count*/ -2,
                    /*expected_text*/ L"",
                    /*expected_count*/ -1);
    EXPECT_UIA_MOVE(text_range_provider, TextUnit_Document,
                    /*count*/ -1,
                    /*expected_text*/ L"",
                    /*expected_count*/ 0);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMove) {
    Init(BuildAXTreeForMove());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    // TODO(https://crbug.com/928948): test intermixed unit types
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveEndpointByDocument) {
    Init(BuildTextDocument({"some text", "more text", "even more text"}));
    AXNode* text_node = GetRootAsAXNode()->children()[1];
  
    // Run the test twice, one for TextUnit_Document and once for TextUnit_Page,
    // since they should have identical behavior.
    const TextUnit textunit_types[] = {TextUnit_Document, TextUnit_Page};
    ComPtr<ITextRangeProvider> text_range_provider;
  
    for (auto& textunit : textunit_types) {
      GetTextRangeProviderFromTextNode(text_range_provider, text_node);
  
      // Verify MoveEndpointByUnit with zero count has no effect
      EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                       TextPatternRangeEndpoint_End, textunit,
                                       /*count*/ 0,
                                       /*expected_text*/ L"more text",
                                       /*expected_count*/ 0);
  
      // Move the endpoint to the end of the document. Verify all text content.
      EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
          text_range_provider, TextPatternRangeEndpoint_End, textunit,
          /*count*/ 1,
          /*expected_text*/ L"more texteven more text",
          /*expected_count*/ 1);
  
      // Verify no moves occur since the end is already at the end of the document
      EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
          text_range_provider, TextPatternRangeEndpoint_End, textunit,
          /*count*/ 5,
          /*expected_text*/ L"more texteven more text",
          /*expected_count*/ 0);
  
      // Move the end before the start
      EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                       TextPatternRangeEndpoint_End, textunit,
                                       /*count*/ -4,
                                       /*expected_text*/ L"",
                                       /*expected_count*/ -1);
  
      // Move the end back to the end of the document. The text content
      // should now include the entire document since end was previously
      // moved before start.
      EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
          text_range_provider, TextPatternRangeEndpoint_End, textunit,
          /*count*/ 1,
          /*expected_text*/ L"some textmore texteven more text",
          /*expected_count*/ 1);
  
      // Move the start point to the end
      EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                       TextPatternRangeEndpoint_Start, textunit,
                                       /*count*/ 3,
                                       /*expected_text*/ L"",
                                       /*expected_count*/ 1);
  
      // Move the start point back to the beginning
      EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
          text_range_provider, TextPatternRangeEndpoint_Start, textunit,
          /*count*/ -3,
          /*expected_text*/ L"some textmore texteven more text",
          /*expected_count*/ -1);
    }
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveEndpointByCharacterMultilingual) {
    // The English string has three characters, each 8 bits in length.
    const std::string english = "hey";
  
    // The Hindi string has two characters, the first one 32 bits and the second
    // 64 bits in length. It is formatted in UTF16.
    const std::string hindi =
        base::UTF16ToUTF8(L"\x0939\x093F\x0928\x094D\x0926\x0940");
  
    // The Thai string has three characters, the first one 48, the second 32 and
    // the last one 16 bits in length. It is formatted in UTF16.
    const std::string thai =
        base::UTF16ToUTF8(L"\x0E23\x0E39\x0E49\x0E2A\x0E36\x0E01");
  
    Init(BuildTextDocument({english, hindi, thai}));
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetRootAsAXNode()->children()[0]);
  
    // Verify MoveEndpointByUnit with zero count has no effect
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"hey");
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 0,
        /*expected_text*/ L"hey",
        /*expected_count*/ 0);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/ L"ey",
        /*expected_count*/ 1);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ -1,
        /*expected_text*/ L"e",
        /*expected_count*/ -1);
  
    // Move end into the adjacent node.
    //
    // The first character of the second node is 32 bits in length.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 2,
        /*expected_text*/ L"ey\x0939\x093F",
        /*expected_count*/ 2);
  
    // The second character of the second node is 64 bits in length.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/ L"ey\x939\x93F\x928\x94D\x926\x940",
        /*expected_count*/ 1);
  
    // Move start into the adjacent node as well.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 2,
        /*expected_text*/ L"\x939\x93F\x928\x94D\x926\x940",
        /*expected_count*/ 2);
  
    // Move end into the last node.
    //
    // The first character of the last node is 48 bits in length.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/ L"\x939\x93F\x928\x94D\x926\x940\xE23\xE39\xE49",
        /*expected_count*/ 1);
  
    // Move end back into the second node and then into the last node again.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ -2,
        /*expected_text*/ L"\x939\x93F",
        /*expected_count*/ -2);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 3,
        /*expected_text*/
        L"\x939\x93F\x928\x94D\x926\x940\xE23\xE39\xE49\xE2A\xE36",
        /*expected_count*/ 3);
  
    // The last character of the last node is only 16 bits in length.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/
        L"\x939\x93F\x928\x94D\x926\x940\xE23\xE39\xE49\xE2A\xE36\xE01",
        /*expected_count*/ 1);
  
    // Move start into the last node.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 3,
        /*expected_text*/ L"\x0E2A\x0E36\x0E01",
        /*expected_count*/ 3);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ -1,
        /*expected_text*/ L"\x0E23\x0E39\x0E49\x0E2A\x0E36\x0E01",
        /*expected_count*/ -1);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveEndpointByWord) {
    Init(BuildTextDocument({"some text", "more text", "even more text"},
                           /*build_word_boundaries_offsets*/ true));
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetRootAsAXNode()->children()[1]);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"more text");
  
    // Moving with zero count does not alter the range.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ 0,
                                     /*expected_text*/ L"more text",
                                     /*expected_count*/ 0);
  
    // Moving the start forward and backward.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
        /*count*/ 1,
        /*expected_text*/ L"text",
        /*expected_count*/ 1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
        /*count*/ -1,
        /*expected_text*/ L"more text",
        /*expected_count*/ -1);
  
    // Moving the end backward and forward.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ -1,
                                     /*expected_text*/ L"more ",
                                     /*expected_count*/ -1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ 1,
                                     /*expected_text*/ L"more text",
                                     /*expected_count*/ 1);
  
    // Moving the start past the end, then reverting.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
        /*count*/ 3,
        /*expected_text*/ L"",
        /*expected_count*/ 3);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
        /*count*/ -3,
        /*expected_text*/ L"more texteven ",
        /*expected_count*/ -3);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ -1,
                                     /*expected_text*/ L"more text",
                                     /*expected_count*/ -1);
  
    // Moving the end past the start, then reverting.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ -3,
                                     /*expected_text*/ L"",
                                     /*expected_count*/ -3);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ 3,
                                     /*expected_text*/ L"textmore text",
                                     /*expected_count*/ 3);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
        /*count*/ 1,
        /*expected_text*/ L"more text",
        /*expected_count*/ 1);
  
    // Moving the endpoints further than both ends of the document.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ 5,
                                     /*expected_text*/ L"more texteven more text",
                                     /*expected_count*/ 3);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
        /*count*/ 6,
        /*expected_text*/ L"",
        /*expected_count*/ 5);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Word,
        /*count*/ -8,
        /*expected_text*/ L"some textmore texteven more text",
        /*expected_count*/ -7);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Word,
                                     /*count*/ -8,
                                     /*expected_text*/ L"",
                                     /*expected_count*/ -7);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveEndpointByLine) {
    Init(BuildTextDocument({"0", "1", "2", "3", "4", "5", "6"}));
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetRootAsAXNode()->children()[3]);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"3");
  
    // Moving with zero count does not alter the range.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Line,
                                     /*count*/ 0,
                                     /*expected_text*/ L"3",
                                     /*expected_count*/ 0);
  
    // Moving the start backward and forward.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
        /*count*/ -2,
        /*expected_text*/ L"123",
        /*expected_count*/ -2);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
        /*count*/ 1,
        /*expected_text*/ L"23",
        /*expected_count*/ 1);
  
    // Moving the end forward and backward.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Line,
                                     /*count*/ 3,
                                     /*expected_text*/ L"23456",
                                     /*expected_count*/ 3);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Line,
                                     /*count*/ -2,
                                     /*expected_text*/ L"234",
                                     /*expected_count*/ -2);
  
    // Moving the end past the start and vice versa.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Line,
                                     /*count*/ -4,
                                     /*expected_text*/ L"",
                                     /*expected_count*/ -4);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
        /*count*/ -1,
        /*expected_text*/ L"0",
        /*expected_count*/ -1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
        /*count*/ 6,
        /*expected_text*/ L"",
        /*expected_count*/ 6);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
        /*count*/ -6,
        /*expected_text*/ L"012345",
        /*expected_count*/ -6);
  
    // Moving the endpoints further than both ends of the document.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Line,
                                     /*count*/ -13,
                                     /*expected_text*/ L"",
                                     /*expected_count*/ -6);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(text_range_provider,
                                     TextPatternRangeEndpoint_End, TextUnit_Line,
                                     /*count*/ 11,
                                     /*expected_text*/ L"0123456",
                                     /*expected_count*/ 7);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
        /*count*/ 9,
        /*expected_text*/ L"",
        /*expected_count*/ 7);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Line,
        /*count*/ -7,
        /*expected_text*/ L"0123456",
        /*expected_count*/ -7);
  }
  
  // Verify that the endpoint can move past an empty text field.
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveEndpointByUnitTextField) {
    ui::AXNodeData root_data;
    root_data.id = 1;
    root_data.role = ax::mojom::Role::kRootWebArea;
  
    ui::AXNodeData group1_data;
    group1_data.id = 2;
    group1_data.role = ax::mojom::Role::kGenericContainer;
  
    ui::AXNodeData text_data;
    text_data.id = 3;
    text_data.role = ax::mojom::Role::kStaticText;
    std::string text_content = "some text";
    text_data.SetName(text_content);
    std::vector<int> word_start_offsets, word_end_offsets;
    ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                 word_end_offsets);
    text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                  word_start_offsets);
    text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                  word_end_offsets);
  
    ui::AXNodeData text_input_data;
    text_input_data.id = 4;
    text_input_data.role = ax::mojom::Role::kTextField;
  
    ui::AXNodeData group2_data;
    group2_data.id = 5;
    group2_data.role = ax::mojom::Role::kGenericContainer;
  
    ui::AXNodeData more_text_data;
    more_text_data.id = 6;
    more_text_data.role = ax::mojom::Role::kStaticText;
    text_content = "more text";
    more_text_data.SetName(text_content);
    ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                 word_end_offsets);
    more_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                       word_start_offsets);
    more_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                       word_end_offsets);
  
    ui::AXNodeData empty_text_data;
    empty_text_data.id = 7;
    empty_text_data.role = ax::mojom::Role::kStaticText;
    text_content = "";
    empty_text_data.SetName(text_content);
    ComputeWordBoundariesOffsets(text_content, word_start_offsets,
                                 word_end_offsets);
    empty_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
                                        word_start_offsets);
    empty_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
                                        word_end_offsets);
  
    root_data.child_ids = {group1_data.id, text_input_data.id, group2_data.id};
    group1_data.child_ids = {text_data.id};
    text_input_data.child_ids = {empty_text_data.id};
    group2_data.child_ids = {more_text_data.id};
  
    ui::AXTreeUpdate update;
    ui::AXTreeData tree_data;
    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.tree_data = tree_data;
    update.has_tree_data = true;
    update.root_id = root_data.id;
    update.nodes = {root_data,   group1_data,    text_data,      text_input_data,
                    group2_data, more_text_data, empty_text_data};
  
    Init(update);
  
    // Set up variables from the tree for testing.
    AXNode* root_node = GetRootAsAXNode();
    AXNode* text_node = root_node->children()[0]->children()[0];
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, text_node);
  
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
  
    int count;
    // Tests for TextUnit_Character
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 1, &count));
    ASSERT_EQ(1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some textm");
  
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -1, &count));
    ASSERT_EQ(-1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
  
    // Tests for TextUnit_Word
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ 1, &count));
    ASSERT_EQ(1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some textmore ");
  
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ -1, &count));
    ASSERT_EQ(-1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
  
    // Tests for TextUnit_Line
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ 1, &count));
    ASSERT_EQ(1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some textmore text");
  
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ -1, &count));
    ASSERT_EQ(-1, count);
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveEndpointByFormat) {
    Init(BuildAXTreeForMoveByFormat());
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, root_node);
  
    EXPECT_UIA_TEXTRANGE_EQ(
        text_range_provider,
        L"Text with formattingStandalone line with no formattingbold "
        L"textParagraph 1Paragraph 2Paragraph 3Paragraph 4");
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ -2,
        /*expected_text*/
        L"Text with formattingStandalone line with no formattingbold "
        L"textParagraph 1",
        /*expected_count*/ -2);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ -1,
        /*expected_text*/
        L"Text with formattingStandalone line with no formattingbold text",
  
        /*expected_count*/ -1);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ -1,
        /*expected_text*/
        L"Text with formattingStandalone line with no formatting",
        /*expected_count*/ -1);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ -1,
        /*expected_text*/ L"Text with formatting",
        /*expected_count*/ -1);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ -1,
        /*expected_text*/ L"",
        /*expected_count*/ -1);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ 7,
        /*expected_text*/
        L"Text with formattingStandalone line with no formattingbold "
        L"textParagraph 1Paragraph 2Paragraph 3Paragraph 4",
        /*expected_count*/ 6);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Format,
        /*count*/ -8,
        /*expected_text*/ L"",
        /*expected_count*/ -6);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderCompare) {
    Init(BuildTextDocument({"some text", "some text"}));
    AXNode* root_node = GetRootAsAXNode();
  
    // Get the textRangeProvider for the document,
    // which contains text "some textsome text".
    ComPtr<ITextRangeProvider> document_text_range_provider;
    GetTextRangeProviderFromTextNode(document_text_range_provider, root_node);
  
    // Get the textRangeProvider for the first text node.
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     root_node->children()[0]);
  
    // Get the textRangeProvider for the second text node.
    ComPtr<ITextRangeProvider> more_text_range_provider;
    GetTextRangeProviderFromTextNode(more_text_range_provider,
                                     root_node->children()[1]);
  
    // Compare text range of the entire document with itself, which should return
    // that they are equal.
    BOOL result;
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->Compare(
        document_text_range_provider.Get(), &result));
    EXPECT_TRUE(result);
  
    // Compare the text range of the entire document with one of its child, which
    // should return that they are not equal.
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->Compare(
        text_range_provider.Get(), &result));
    EXPECT_FALSE(result);
  
    // Compare the text range of text_node which contains "some text" with
    // text range of more_text_node which also contains "some text". Those two
    // text ranges should not equal, because their endpoints are different, even
    // though their contents are the same.
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->Compare(more_text_range_provider.Get(), &result));
    EXPECT_FALSE(result);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderSelection) {
    Init(BuildTextDocument({"some text"}));
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, GetRootAsAXNode());
  
    ASSERT_UIA_INVALIDOPERATION(text_range_provider->AddToSelection());
    ASSERT_UIA_INVALIDOPERATION(text_range_provider->RemoveFromSelection());
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderGetBoundingRectangles) {
    ui::AXTreeUpdate update = BuildAXTreeForBoundingRectangles();
    Init(update);
    ComPtr<ITextRangeProvider> text_range_provider;
    base::win::ScopedSafearray rectangles;
    int count;
  
    // Expected bounding rects:
    // <button>Button</button><input type="checkbox">Line 1<br>Line 2
    // |---------------------||---------------------||----|   |------|
    GetTextRangeProviderFromTextNode(text_range_provider, GetRootAsAXNode());
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetBoundingRectangles(rectangles.Receive()));
    std::vector<double> expected_values = {20,  20, 200, 30, /* button */
                                           20,  50, 200, 30, /* check box */
                                           220, 20, 30,  30, /* line 1 */
                                           220, 50, 42,  30 /* line 2 */};
    EXPECT_UIA_SAFEARRAY_EQ(rectangles.Get(), expected_values);
    rectangles.Reset();
  
    // Move the text range end back by one character.
    // Expected bounding rects:
    // <button>Button</button><input type="checkbox">Line 1<br>Line 2
    // |---------------------||---------------------||----|   |----|
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -1, &count));
    ASSERT_EQ(-1, count);
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetBoundingRectangles(rectangles.Receive()));
    expected_values = {20,  20, 200, 30, /* button */
                       20,  50, 200, 30, /* check box */
                       220, 20, 30,  30, /* line 1 */
                       220, 50, 35,  30 /* line 2 */};
    EXPECT_UIA_SAFEARRAY_EQ(rectangles.Get(), expected_values);
    rectangles.Reset();
  
    // Move the text range end back by one line.
    // Expected bounding rects:
    // <button>Button</button><input type="checkbox">Line 1<br>Line 2
    // |---------------------||---------------------||-----|
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ -1, &count));
    ASSERT_EQ(-1, count);
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetBoundingRectangles(rectangles.Receive()));
    expected_values = {20,  20, 200, 30, /* button */
                       20,  50, 200, 30, /* check box */
                       220, 20, 30,  30 /* line 1 */};
    EXPECT_UIA_SAFEARRAY_EQ(rectangles.Get(), expected_values);
    rectangles.Reset();
  
    // Move the text range end back by one line.
    // Expected bounding rects:
    // <button>Button</button><input type="checkbox">Line 1<br>Line 2
    // |---------------------||---------------------|
    ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
        TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ -2, &count));
    ASSERT_EQ(-2, count);
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetBoundingRectangles(rectangles.Receive()));
    expected_values = {20, 20, 200, 30, /* button */
                       20, 50, 200, 30 /* check box */};
    EXPECT_UIA_SAFEARRAY_EQ(rectangles.Get(), expected_values);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderGetEnclosingElement) {
    // Set up ax tree with the following structure:
    //
    // root
    // |
    // paragraph______________________________________________
    // |               |            |       |                |
    // static_text     link         link    search input     pdf_highlight
    // |               |            |       |                |
    // text_node       static_text  ul      text_node        static_text
    //                 |            |                        |
    //                 text_node    li                       text_node
    //                              |
    //                              static_text
    //                              |
    //                              text_node
  
    ui::AXNodeData root_data;
    root_data.id = 1;
    root_data.role = ax::mojom::Role::kRootWebArea;
  
    ui::AXNodeData paragraph_data;
    paragraph_data.id = 2;
    paragraph_data.role = ax::mojom::Role::kParagraph;
    root_data.child_ids.push_back(paragraph_data.id);
  
    ui::AXNodeData static_text_data1;
    static_text_data1.id = 3;
    static_text_data1.role = ax::mojom::Role::kStaticText;
    paragraph_data.child_ids.push_back(static_text_data1.id);
  
    ui::AXNodeData inline_text_data1;
    inline_text_data1.id = 4;
    inline_text_data1.role = ax::mojom::Role::kInlineTextBox;
    static_text_data1.child_ids.push_back(inline_text_data1.id);
  
    ui::AXNodeData link_data;
    link_data.id = 5;
    link_data.role = ax::mojom::Role::kLink;
    paragraph_data.child_ids.push_back(link_data.id);
  
    ui::AXNodeData static_text_data2;
    static_text_data2.id = 6;
    static_text_data2.role = ax::mojom::Role::kStaticText;
    link_data.child_ids.push_back(static_text_data2.id);
  
    ui::AXNodeData inline_text_data2;
    inline_text_data2.id = 7;
    inline_text_data2.role = ax::mojom::Role::kInlineTextBox;
    static_text_data2.child_ids.push_back(inline_text_data2.id);
  
    ui::AXNodeData link_data2;
    link_data2.id = 8;
    link_data2.role = ax::mojom::Role::kLink;
    paragraph_data.child_ids.push_back(link_data2.id);
  
    ui::AXNodeData list_data;
    list_data.id = 9;
    list_data.role = ax::mojom::Role::kList;
    link_data2.child_ids.push_back(list_data.id);
  
    ui::AXNodeData list_item_data;
    list_item_data.id = 10;
    list_item_data.role = ax::mojom::Role::kListItem;
    list_data.child_ids.push_back(list_item_data.id);
  
    ui::AXNodeData static_text_data3;
    static_text_data3.id = 11;
    static_text_data3.role = ax::mojom::Role::kStaticText;
    list_item_data.child_ids.push_back(static_text_data3.id);
  
    ui::AXNodeData inline_text_data3;
    inline_text_data3.id = 12;
    inline_text_data3.role = ax::mojom::Role::kInlineTextBox;
    static_text_data3.child_ids.push_back(inline_text_data3.id);
  
    ui::AXNodeData search_box;
    search_box.id = 13;
    search_box.role = ax::mojom::Role::kSearchBox;
    paragraph_data.child_ids.push_back(search_box.id);
  
    ui::AXNodeData search_text;
    search_text.id = 14;
    search_text.role = ax::mojom::Role::kStaticText;
    search_text.SetName("placeholder");
    search_box.child_ids.push_back(search_text.id);
  
    ui::AXNodeData pdf_highlight_data;
    pdf_highlight_data.id = 15;
    pdf_highlight_data.role = ax::mojom::Role::kPdfActionableHighlight;
    paragraph_data.child_ids.push_back(pdf_highlight_data.id);
  
    ui::AXNodeData static_text_data4;
    static_text_data4.id = 16;
    static_text_data4.role = ax::mojom::Role::kStaticText;
    pdf_highlight_data.child_ids.push_back(static_text_data4.id);
  
    ui::AXNodeData inline_text_data4;
    inline_text_data4.id = 17;
    inline_text_data4.role = ax::mojom::Role::kInlineTextBox;
    static_text_data4.child_ids.push_back(inline_text_data4.id);
  
    ui::AXTreeUpdate update;
    ui::AXTreeData tree_data;
    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.tree_data = tree_data;
    update.has_tree_data = true;
    update.root_id = root_data.id;
    update.nodes = {root_data,         paragraph_data,    static_text_data1,
                    inline_text_data1, link_data,         static_text_data2,
                    inline_text_data2, link_data2,        list_data,
                    list_item_data,    static_text_data3, inline_text_data3,
                    search_box,        search_text,       pdf_highlight_data,
                    static_text_data4, inline_text_data4};
    Init(update);
  
    // Set up variables from the tree for testing.
    AXNode* paragraph_node = GetRootAsAXNode()->children()[0];
    AXNode* static_text_node1 = paragraph_node->children()[0];
    AXNode* link_node = paragraph_node->children()[1];
    AXNode* inline_text_node1 = static_text_node1->children()[0];
    AXNode* static_text_node2 = link_node->children()[0];
    AXNode* inline_text_node2 = static_text_node2->children()[0];
    AXNode* link_node2 = paragraph_node->children()[2];
    AXNode* list_node = link_node2->children()[0];
    AXNode* list_item_node = list_node->children()[0];
    AXNode* static_text_node3 = list_item_node->children()[0];
    AXNode* inline_text_node3 = static_text_node3->children()[0];
    AXNode* search_box_node = paragraph_node->children()[3];
    AXNode* search_text_node = search_box_node->children()[0];
    AXNode* pdf_highlight_node = paragraph_node->children()[4];
    AXNode* static_text_node4 = pdf_highlight_node->children()[0];
    AXNode* inline_text_node4 = static_text_node4->children()[0];
  
    ComPtr<IRawElementProviderSimple> link_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(link_node);
    ComPtr<IRawElementProviderSimple> static_text_node_raw1 =
        QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_node1);
    ComPtr<IRawElementProviderSimple> static_text_node_raw2 =
        QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_node2);
    ComPtr<IRawElementProviderSimple> static_text_node_raw3 =
        QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_node3);
    ComPtr<IRawElementProviderSimple> inline_text_node_raw1 =
        QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node1);
    ComPtr<IRawElementProviderSimple> inline_text_node_raw2 =
        QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node2);
    ComPtr<IRawElementProviderSimple> inline_text_node_raw3 =
        QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node3);
    ComPtr<IRawElementProviderSimple> search_box_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(search_box_node);
    ComPtr<IRawElementProviderSimple> search_text_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(search_text_node);
    ComPtr<IRawElementProviderSimple> pdf_highlight_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(pdf_highlight_node);
    ComPtr<IRawElementProviderSimple> inline_text_node_raw4 =
        QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node4);
  
    // Test GetEnclosingElement for the two leaves text nodes. The enclosing
    // element of the first one should be its static text parent (because inline
    // text boxes shouldn't be exposed) and the enclosing element for the text
    // node that is grandchild of the link node should return the link node.
    // The text node in the link node with a complex subtree should behave
    // normally and return the static text parent.
    ComPtr<ITextProvider> text_provider;
    EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw1->GetPatternProvider(
        UIA_TextPatternId, &text_provider));
  
    ComPtr<ITextRangeProvider> text_range_provider;
    EXPECT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
  
    ComPtr<IRawElementProviderSimple> enclosing_element;
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetEnclosingElement(&enclosing_element));
    EXPECT_EQ(static_text_node_raw1.Get(), enclosing_element.Get());
  
    EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw2->GetPatternProvider(
        UIA_TextPatternId, &text_provider));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetEnclosingElement(&enclosing_element));
    EXPECT_EQ(link_node_raw.Get(), enclosing_element.Get());
  
    EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw3->GetPatternProvider(
        UIA_TextPatternId, &text_provider));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetEnclosingElement(&enclosing_element));
    EXPECT_EQ(static_text_node_raw3.Get(), enclosing_element.Get());
  
    // The enclosing element of a text range in the search text should give the
    // search box
    EXPECT_HRESULT_SUCCEEDED(search_text_node_raw->GetPatternProvider(
        UIA_TextPatternId, &text_provider));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetEnclosingElement(&enclosing_element));
    EXPECT_EQ(search_box_node_raw.Get(), enclosing_element.Get());
  
    // The enclosing element for the text node that is grandchild of the
    // pdf_highlight node should return the pdf_highlight node.
    EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw4->GetPatternProvider(
        UIA_TextPatternId, &text_provider));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider->GetEnclosingElement(&enclosing_element));
    EXPECT_EQ(pdf_highlight_node_raw.Get(), enclosing_element.Get());
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderMoveEndpointByRange) {
    Init(BuildTextDocument({"some text", "more text"}));
  
    AXNode* root_node = GetRootAsAXNode();
    AXNode* text_node = root_node->children()[0];
    AXNode* more_text_node = root_node->children()[1];
  
    // Text range for the document, which contains text "some textmore text".
    ComPtr<IRawElementProviderSimple> root_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
    ComPtr<ITextProvider> document_provider;
    EXPECT_HRESULT_SUCCEEDED(
        root_node_raw->GetPatternProvider(UIA_TextPatternId, &document_provider));
    ComPtr<ITextRangeProvider> document_text_range_provider;
    ComPtr<AXPlatformNodeTextRangeProviderWin> document_text_range;
  
    // Text range related to "some text".
    ComPtr<IRawElementProviderSimple> text_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(text_node);
    ComPtr<ITextProvider> text_provider;
    EXPECT_HRESULT_SUCCEEDED(
        text_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
    ComPtr<ITextRangeProvider> text_range_provider;
    ComPtr<AXPlatformNodeTextRangeProviderWin> text_range;
  
    // Text range related to "more text".
    ComPtr<IRawElementProviderSimple> more_text_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(more_text_node);
    ComPtr<ITextProvider> more_text_provider;
    EXPECT_HRESULT_SUCCEEDED(more_text_node_raw->GetPatternProvider(
        UIA_TextPatternId, &more_text_provider));
    ComPtr<ITextRangeProvider> more_text_range_provider;
    ComPtr<AXPlatformNodeTextRangeProviderWin> more_text_range;
  
    // Move the start of document text range "some textmore text" to the end of
    // itself.
    // The start of document text range "some textmore text" is at the end of
    // itself.
    //
    // Before:
    // |s                e|
    // "some textmore text"
    // After:
    //                  |s
    //                   e|
    // "some textmore text"
  
    // Get the textRangeProvider for the document, which contains text
    // "some textmore text".
    EXPECT_HRESULT_SUCCEEDED(
        document_provider->get_DocumentRange(&document_text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->MoveEndpointByRange(
        TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_End));
  
    document_text_range_provider->QueryInterface(
        IID_PPV_ARGS(&document_text_range));
    EXPECT_EQ(*GetStart(document_text_range.Get()),
              *GetEnd(document_text_range.Get()));
  
    // Move the end of document text range "some textmore text" to the start of
    // itself.
    // The end of document text range "some textmore text" is at the start of
    // itself.
    //
    // Before:
    // |s                e|
    // "some textmore text"
    // After:
    // |s
    //  e|
    // "some textmore text"
  
    // Get the textRangeProvider for the document, which contains text
    // "some textmore text".
    EXPECT_HRESULT_SUCCEEDED(
        document_provider->get_DocumentRange(&document_text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->MoveEndpointByRange(
        TextPatternRangeEndpoint_Start, document_text_range_provider.Get(),
        TextPatternRangeEndpoint_End));
  
    document_text_range_provider->QueryInterface(
        IID_PPV_ARGS(&document_text_range));
    EXPECT_EQ(*GetStart(document_text_range.Get()),
              *GetEnd(document_text_range.Get()));
  
    // Move the start of document text range "some textmore text" to the start
    // of text range "more text". The start of document text range "some
    // textmore text" is at the start of text range "more text". The end of
    // document range does not change.
    //
    // Before:
    // |s                e|
    // "some textmore text"
    // After:
    //          |s       e|
    // "some textmore text"
  
    // Get the textRangeProvider for the document, which contains text
    // "some textmore text".
    EXPECT_HRESULT_SUCCEEDED(
        document_provider->get_DocumentRange(&document_text_range_provider));
    // Get the textRangeProvider for more_text_node which contains "more text".
    EXPECT_HRESULT_SUCCEEDED(
        more_text_provider->get_DocumentRange(&more_text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->MoveEndpointByRange(
        TextPatternRangeEndpoint_Start, more_text_range_provider.Get(),
        TextPatternRangeEndpoint_Start));
  
    document_text_range_provider->QueryInterface(
        IID_PPV_ARGS(&document_text_range));
    more_text_range_provider->QueryInterface(IID_PPV_ARGS(&more_text_range));
    EXPECT_EQ(*GetStart(document_text_range.Get()),
              *GetStart(more_text_range.Get()));
  
    // Move the end of document text range "some textmore text" to the end of
    // text range "some text".
    // The end of document text range "some textmore text" is at the end of text
    // range "some text". The start of document range does not change.
    //
    // Before:
    // |s                e|
    // "some textmore text"
    // After:
    // |s       e|
    // "some textmore text"
  
    // Get the textRangeProvider for the document, which contains text
    // "some textmore text".
    EXPECT_HRESULT_SUCCEEDED(
        document_provider->get_DocumentRange(&document_text_range_provider));
    // Get the textRangeProvider for text_node which contains "some text".
    EXPECT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(document_text_range_provider->MoveEndpointByRange(
        TextPatternRangeEndpoint_End, text_range_provider.Get(),
        TextPatternRangeEndpoint_End));
  
    document_text_range_provider->QueryInterface(
        IID_PPV_ARGS(&document_text_range));
    text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range));
    EXPECT_EQ(*GetEnd(document_text_range.Get()), *GetEnd(text_range.Get()));
  
    // Move the end of text range "more text" to the start of
    // text range "some text". Since the order of the endpoints being moved
    // (those of "more text") have to be ensured, both endpoints of "more text"
    // is at the start of "some text".
    //
    // Before:
    //          |s       e|
    // "some textmore text"
    // After:
    //  e|
    // |s
    // "some textmore text"
  
    // Get the textRangeProvider for text_node which contains "some text".
    EXPECT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
    // Get the textRangeProvider for more_text_node which contains "more text".
    EXPECT_HRESULT_SUCCEEDED(
        more_text_provider->get_DocumentRange(&more_text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(more_text_range_provider->MoveEndpointByRange(
        TextPatternRangeEndpoint_End, text_range_provider.Get(),
        TextPatternRangeEndpoint_Start));
  
    text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range));
    more_text_range_provider->QueryInterface(IID_PPV_ARGS(&more_text_range));
    EXPECT_EQ(*GetEnd(more_text_range.Get()), *GetStart(text_range.Get()));
    EXPECT_EQ(*GetStart(more_text_range.Get()), *GetStart(text_range.Get()));
  
    // Move the start of text range "some text" to the end of text range
    // "more text". Since the order of the endpoints being moved (those
    // of "some text") have to be ensured, both endpoints of "some text" is at
    // the end of "more text".
    //
    // Before:
    // |s       e|
    // "some textmore text"
    // After:
    //                  |s
    //                   e|
    // "some textmore text"
  
    // Get the textRangeProvider for text_node which contains "some text".
    EXPECT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
    // Get the textRangeProvider for more_text_node which contains "more text".
    EXPECT_HRESULT_SUCCEEDED(
        more_text_provider->get_DocumentRange(&more_text_range_provider));
  
    EXPECT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByRange(
        TextPatternRangeEndpoint_Start, more_text_range_provider.Get(),
        TextPatternRangeEndpoint_End));
  
    text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range));
    more_text_range_provider->QueryInterface(IID_PPV_ARGS(&more_text_range));
    EXPECT_EQ(*GetStart(text_range.Get()), *GetEnd(more_text_range.Get()));
    EXPECT_EQ(*GetEnd(text_range.Get()), *GetEnd(more_text_range.Get()));
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetChildren) {
    // Set up ax tree with the following structure:
    //
    // ++1 kRootWebArea
    // ++++2 kDocument ignored
    // ++++++3 kStaticText
    // ++++++++4 kInlineTextBox
    // ++++++++5 kInlineTextBox
    // ++++++6 kStaticText
    // ++++++7 kStaticText ignored
    // ++++++8 kButton
    // ++++++++9  kImage
    // ++++++++10 kStaticText
  
    AXNodeData root_1;
    AXNodeData document_2;
    AXNodeData static_text_3;
    AXNodeData inline_box_4;
    AXNodeData inline_box_5;
    AXNodeData static_text_6;
    AXNodeData static_text_7;
    AXNodeData button_8;
    AXNodeData image_9;
    AXNodeData static_text_10;
  
    root_1.id = 1;
    document_2.id = 2;
    static_text_3.id = 3;
    inline_box_4.id = 4;
    inline_box_5.id = 5;
    static_text_6.id = 6;
    static_text_7.id = 7;
    button_8.id = 8;
    image_9.id = 9;
    static_text_10.id = 10;
  
    root_1.role = ax::mojom::Role::kRootWebArea;
    root_1.child_ids = {document_2.id};
  
    document_2.role = ax::mojom::Role::kDocument;
    document_2.AddState(ax::mojom::State::kIgnored);
    document_2.child_ids = {static_text_3.id, static_text_6.id, static_text_7.id,
                            button_8.id};
  
    static_text_3.role = ax::mojom::Role::kStaticText;
    static_text_3.child_ids = {inline_box_4.id, inline_box_5.id};
  
    inline_box_4.role = ax::mojom::Role::kInlineTextBox;
  
    inline_box_5.role = ax::mojom::Role::kInlineTextBox;
  
    static_text_6.role = ax::mojom::Role::kStaticText;
  
    static_text_7.role = ax::mojom::Role::kStaticText;
    static_text_7.AddState(ax::mojom::State::kIgnored);
  
    button_8.role = ax::mojom::Role::kButton;
    // Hack: This attribute is needed to be able to get a text range provider
    // located on this element (see AXPlatformNodeWin::GetPatternProvider).
    button_8.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
    button_8.child_ids = {image_9.id, static_text_10.id};
  
    image_9.role = ax::mojom::Role::kImage;
  
    static_text_10.role = ax::mojom::Role::kStaticText;
  
    ui::AXTreeUpdate update;
    ui::AXTreeData tree_data;
    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.tree_data = tree_data;
    update.has_tree_data = true;
    update.root_id = root_1.id;
    update.nodes.push_back(root_1);
    update.nodes.push_back(document_2);
    update.nodes.push_back(static_text_3);
    update.nodes.push_back(inline_box_4);
    update.nodes.push_back(inline_box_5);
    update.nodes.push_back(static_text_6);
    update.nodes.push_back(static_text_7);
    update.nodes.push_back(button_8);
    update.nodes.push_back(image_9);
    update.nodes.push_back(static_text_10);
  
    Init(update);
  
    // Set up variables from the tree for testing.
    AXNode* document_2_node = GetRootAsAXNode()->children()[0];
    AXNode* static_text_3_node = document_2_node->children()[0];
    AXNode* inline_box_4_node = static_text_3_node->children()[0];
    AXNode* inline_box_5_node = static_text_3_node->children()[1];
    AXNode* static_text_6_node = document_2_node->children()[1];
    AXNode* button_8_node = document_2_node->children()[3];
  
    ComPtr<IRawElementProviderSimple> document_2_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(document_2_node);
    ComPtr<IRawElementProviderSimple> static_text_3_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_3_node);
    ComPtr<IRawElementProviderSimple> inline_box_4_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(inline_box_4_node);
    ComPtr<IRawElementProviderSimple> inline_box_5_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(inline_box_5_node);
    ComPtr<IRawElementProviderSimple> static_text_6_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_6_node);
    ComPtr<IRawElementProviderSimple> button_8_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(button_8_node);
  
    ComPtr<ITextProvider> text_provider;
    ComPtr<ITextRangeProvider> text_range_provider;
    base::win::ScopedSafearray children;
    std::vector<ComPtr<IRawElementProviderSimple>> expected_values = {};
  
    // Test inline_box_4 - a leaf node should have no children.
    {
      EXPECT_HRESULT_SUCCEEDED(inline_box_4_raw->GetPatternProvider(
          UIA_TextPatternId, &text_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_provider->get_DocumentRange(&text_range_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_range_provider->GetChildren(children.Receive()));
  
      expected_values = {};
  
      EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values);
    }
  
    // Test static_text_6 - a leaf node should have no children.
    {
      EXPECT_HRESULT_SUCCEEDED(static_text_6_raw->GetPatternProvider(
          UIA_TextPatternId, &text_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_provider->get_DocumentRange(&text_range_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_range_provider->GetChildren(children.Receive()));
  
      expected_values = {};
  
      EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values);
    }
  
    // Test static_text_3 - children should include inline_box_4 and inline_box_5.
    {
      EXPECT_HRESULT_SUCCEEDED(static_text_3_raw->GetPatternProvider(
          UIA_TextPatternId, &text_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_provider->get_DocumentRange(&text_range_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_range_provider->GetChildren(children.Receive()));
  
      expected_values = {inline_box_4_raw, inline_box_5_raw};
  
      EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values);
    }
  
    // Test button_8 - a button should never expose its children.
    {
      EXPECT_HRESULT_SUCCEEDED(
          button_8_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_provider->get_DocumentRange(&text_range_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_range_provider->GetChildren(children.Receive()));
  
      expected_values = {};
  
      EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values);
    }
  
    // Test document_2 - children should not include ignored nodes and nodes under
    // a node that should hide its children.
    {
      EXPECT_HRESULT_SUCCEEDED(
          document_2_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_provider->get_DocumentRange(&text_range_provider));
  
      EXPECT_HRESULT_SUCCEEDED(
          text_range_provider->GetChildren(children.Receive()));
  
      expected_values = {
          static_text_3_raw, inline_box_4_raw, inline_box_5_raw,
          static_text_6_raw, button_8_raw,
      };
  
      EXPECT_UIA_SAFEARRAY_EQ(children.Get(), expected_values);
    }
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderGetAttributeValue) {
    ui::AXNodeData text_data;
    text_data.id = 2;
    text_data.role = ax::mojom::Role::kStaticText;
    text_data.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, "sans");
    text_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, 16);
    text_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontWeight, 300);
    text_data.AddIntAttribute(ax::mojom::IntAttribute::kTextOverlineStyle, 1);
    text_data.AddIntAttribute(ax::mojom::IntAttribute::kTextStrikethroughStyle,
                              2);
    text_data.AddIntAttribute(ax::mojom::IntAttribute::kTextUnderlineStyle, 3);
    text_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                              0xDEADBEEFU);
    text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
    text_data.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr-CA");
    text_data.SetTextDirection(ax::mojom::WritingDirection::kRtl);
    text_data.AddTextStyle(ax::mojom::TextStyle::kItalic);
    text_data.SetTextPosition(ax::mojom::TextPosition::kSubscript);
    text_data.SetRestriction(ax::mojom::Restriction::kReadOnly);
    text_data.SetTextAlign(ax::mojom::TextAlign::kCenter);
    text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes,
                                  {(int)ax::mojom::MarkerType::kGrammar,
                                   (int)ax::mojom::MarkerType::kSpelling});
    text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts,
                                  {0, 5});
    text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds,
                                  {9, 9});
    text_data.SetName("some text");
  
    ui::AXNodeData heading_data;
    heading_data.id = 3;
    heading_data.role = ax::mojom::Role::kHeading;
    heading_data.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, 6);
    heading_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                 0xDEADBEEFU);
    heading_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
    heading_data.SetTextDirection(ax::mojom::WritingDirection::kRtl);
    heading_data.SetTextPosition(ax::mojom::TextPosition::kSuperscript);
    heading_data.AddState(ax::mojom::State::kEditable);
    heading_data.child_ids = {4};
  
    ui::AXNodeData heading_text_data;
    heading_text_data.id = 4;
    heading_text_data.role = ax::mojom::Role::kStaticText;
    heading_text_data.AddState(ax::mojom::State::kInvisible);
    heading_text_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                      0xDEADBEEFU);
    heading_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor,
                                      0xDEADC0DEU);
    heading_text_data.SetTextDirection(ax::mojom::WritingDirection::kRtl);
    heading_text_data.SetTextPosition(ax::mojom::TextPosition::kSuperscript);
    heading_text_data.AddState(ax::mojom::State::kEditable);
    heading_text_data.SetTextAlign(ax::mojom::TextAlign::kJustify);
    heading_text_data.AddIntListAttribute(
        ax::mojom::IntListAttribute::kMarkerTypes,
        {(int)ax::mojom::MarkerType::kSpelling});
    heading_text_data.AddIntListAttribute(
        ax::mojom::IntListAttribute::kMarkerStarts, {5});
    heading_text_data.AddIntListAttribute(
        ax::mojom::IntListAttribute::kMarkerEnds, {9});
    heading_text_data.SetName("more text");
  
    ui::AXNodeData mark_data;
    mark_data.id = 5;
    mark_data.role = ax::mojom::Role::kMark;
    mark_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                              0xDEADBEEFU);
    mark_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
    mark_data.SetTextDirection(ax::mojom::WritingDirection::kRtl);
    mark_data.child_ids = {6};
  
    ui::AXNodeData mark_text_data;
    mark_text_data.id = 6;
    mark_text_data.role = ax::mojom::Role::kStaticText;
    mark_text_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                   0xDEADBEEFU);
    mark_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
    mark_text_data.SetTextDirection(ax::mojom::WritingDirection::kRtl);
    mark_text_data.SetTextAlign(ax::mojom::TextAlign::kNone);
    mark_text_data.SetName("marked text");
  
    ui::AXNodeData list_data;
    list_data.id = 7;
    list_data.role = ax::mojom::Role::kList;
    list_data.child_ids = {8, 10};
    list_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                              0xDEADBEEFU);
    list_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
  
    ui::AXNodeData list_item_data;
    list_item_data.id = 8;
    list_item_data.role = ax::mojom::Role::kListItem;
    list_item_data.child_ids = {9};
    list_item_data.AddIntAttribute(
        ax::mojom::IntAttribute::kListStyle,
        static_cast<int>(ax::mojom::ListStyle::kOther));
    list_item_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                   0xDEADBEEFU);
    list_item_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
  
    ui::AXNodeData list_item_text_data;
    list_item_text_data.id = 9;
    list_item_text_data.role = ax::mojom::Role::kStaticText;
    list_item_text_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                        0xDEADBEEFU);
    list_item_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor,
                                        0xDEADC0DEU);
    list_item_text_data.SetName("list item");
  
    ui::AXNodeData list_item2_data;
    list_item2_data.id = 10;
    list_item2_data.role = ax::mojom::Role::kListItem;
    list_item2_data.child_ids = {11};
    list_item2_data.AddIntAttribute(
        ax::mojom::IntAttribute::kListStyle,
        static_cast<int>(ax::mojom::ListStyle::kDisc));
    list_item2_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                    0xDEADBEEFU);
    list_item2_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
  
    ui::AXNodeData list_item2_text_data;
    list_item2_text_data.id = 11;
    list_item2_text_data.role = ax::mojom::Role::kStaticText;
    list_item2_text_data.AddIntAttribute(
        ax::mojom::IntAttribute::kBackgroundColor, 0xDEADBEEFU);
    list_item2_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor,
                                         0xDEADC0DEU);
    list_item2_text_data.SetName("list item 2");
  
    ui::AXNodeData input_text_data;
    input_text_data.id = 12;
    input_text_data.role = ax::mojom::Role::kTextField;
    input_text_data.AddState(ax::mojom::State::kEditable);
    input_text_data.AddIntAttribute(
        ax::mojom::IntAttribute::kNameFrom,
        static_cast<int>(ax::mojom::NameFrom::kPlaceholder));
    input_text_data.AddStringAttribute(ax::mojom::StringAttribute::kPlaceholder,
                                       "placeholder2");
    input_text_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                    0xDEADBEEFU);
    input_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
    input_text_data.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot,
                                     true);
    input_text_data.SetName("placeholder");
    input_text_data.child_ids = {13};
  
    ui::AXNodeData placeholder_text_data;
    placeholder_text_data.id = 13;
    placeholder_text_data.role = ax::mojom::Role::kStaticText;
    placeholder_text_data.AddIntAttribute(
        ax::mojom::IntAttribute::kBackgroundColor, 0xDEADBEEFU);
    placeholder_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor,
                                          0xDEADC0DEU);
    placeholder_text_data.SetName("placeholder");
  
    ui::AXNodeData input_text_data2;
    input_text_data2.id = 14;
    input_text_data2.role = ax::mojom::Role::kTextField;
    input_text_data2.AddState(ax::mojom::State::kEditable);
    input_text_data2.AddStringAttribute(ax::mojom::StringAttribute::kPlaceholder,
                                        "placeholder2");
    input_text_data2.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                     0xDEADBEEFU);
    input_text_data2.AddIntAttribute(ax::mojom::IntAttribute::kColor,
                                     0xDEADC0DEU);
    input_text_data2.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot,
                                      true);
    input_text_data2.SetName("foo");
    input_text_data2.child_ids = {15};
  
    ui::AXNodeData placeholder_text_data2;
    placeholder_text_data2.id = 15;
    placeholder_text_data2.role = ax::mojom::Role::kStaticText;
    placeholder_text_data2.AddIntAttribute(
        ax::mojom::IntAttribute::kBackgroundColor, 0xDEADBEEFU);
    placeholder_text_data2.AddIntAttribute(ax::mojom::IntAttribute::kColor,
                                           0xDEADC0DEU);
    placeholder_text_data2.SetName("placeholder2");
  
    ui::AXNodeData link_data;
    link_data.id = 16;
    link_data.role = ax::mojom::Role::kLink;
    link_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                              0xDEADBEEFU);
    link_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
  
    ui::AXNodeData link_text_data;
    link_text_data.id = 17;
    link_text_data.role = ax::mojom::Role::kStaticText;
    link_text_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
                                   0xDEADBEEFU);
    link_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
    link_data.child_ids = {17};
  
    ui::AXNodeData root_data;
    root_data.id = 1;
    root_data.role = ax::mojom::Role::kRootWebArea;
    root_data.child_ids = {2, 3, 5, 7, 12, 14, 16};
  
    ui::AXTreeUpdate update;
    ui::AXTreeData tree_data;
    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.tree_data = tree_data;
    update.has_tree_data = true;
    update.root_id = root_data.id;
    update.nodes.push_back(root_data);
    update.nodes.push_back(text_data);
    update.nodes.push_back(heading_data);
    update.nodes.push_back(heading_text_data);
    update.nodes.push_back(mark_data);
    update.nodes.push_back(mark_text_data);
    update.nodes.push_back(list_data);
    update.nodes.push_back(list_item_data);
    update.nodes.push_back(list_item_text_data);
    update.nodes.push_back(list_item2_data);
    update.nodes.push_back(list_item2_text_data);
    update.nodes.push_back(input_text_data);
    update.nodes.push_back(placeholder_text_data);
    update.nodes.push_back(input_text_data2);
    update.nodes.push_back(placeholder_text_data2);
    update.nodes.push_back(link_data);
    update.nodes.push_back(link_text_data);
  
    Init(update);
  
    AXNode* root_node = GetRootAsAXNode();
    AXNode* text_node = root_node->children()[0];
    AXNode* heading_node = root_node->children()[1];
    AXNode* heading_text_node = heading_node->children()[0];
    AXNode* mark_node = root_node->children()[2];
    AXNode* mark_text_node = mark_node->children()[0];
    AXNode* list_node = root_node->children()[3];
    AXNode* list_item_node = list_node->children()[0];
    AXNode* list_item_text_node = list_item_node->children()[0];
    AXNode* list_item2_node = list_node->children()[1];
    AXNode* list_item2_text_node = list_item2_node->children()[0];
    AXNode* input_text_node = root_node->children()[4];
    AXNode* placeholder_text_node = input_text_node->children()[0];
    AXNode* input_text_node2 = root_node->children()[5];
    AXNode* placeholder_text_node2 = input_text_node2->children()[0];
    AXNode* link_node = root_node->children()[6];
    AXNode* link_text_node = link_node->children()[0];
  
    ComPtr<ITextRangeProvider> document_range_provider;
    GetTextRangeProviderFromTextNode(document_range_provider, root_node);
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider, text_node);
    ComPtr<ITextRangeProvider> heading_text_range_provider;
    GetTextRangeProviderFromTextNode(heading_text_range_provider,
                                     heading_text_node);
    ComPtr<ITextRangeProvider> mark_text_range_provider;
    GetTextRangeProviderFromTextNode(mark_text_range_provider, mark_text_node);
    ComPtr<ITextRangeProvider> list_item_text_range_provider;
    GetTextRangeProviderFromTextNode(list_item_text_range_provider,
                                     list_item_text_node);
    ComPtr<ITextRangeProvider> list_item2_text_range_provider;
    GetTextRangeProviderFromTextNode(list_item2_text_range_provider,
                                     list_item2_text_node);
  
    ComPtr<ITextRangeProvider> placeholder_text_range_provider;
    GetTextRangeProviderFromTextNode(placeholder_text_range_provider,
                                     placeholder_text_node);
  
    ComPtr<ITextRangeProvider> placeholder_text_range_provider2;
    GetTextRangeProviderFromTextNode(placeholder_text_range_provider2,
                                     placeholder_text_node2);
  
    ComPtr<ITextRangeProvider> link_text_range_provider;
    GetTextRangeProviderFromTextNode(link_text_range_provider, link_text_node);
  
    base::win::ScopedVariant expected_variant;
  
    // SkColor is ARGB, COLORREF is 0BGR
    expected_variant.Set(static_cast<int32_t>(0x00EFBEADU));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
                                UIA_BackgroundColorAttributeId, expected_variant);
    // Important: all nodes need to have the kColor and kBackgroundColor attribute
    // set for this test, otherwise the following assert will fail.
    EXPECT_UIA_TEXTATTRIBUTE_EQ(document_range_provider,
                                UIA_BackgroundColorAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(static_cast<int32_t>(BulletStyle::BulletStyle_None));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(list_item_text_range_provider,
                                UIA_BulletStyleAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(
        static_cast<int32_t>(BulletStyle::BulletStyle_FilledRoundBullet));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(list_item2_text_range_provider,
                                UIA_BulletStyleAttributeId, expected_variant);
    expected_variant.Reset();
  
    {
      base::win::ScopedVariant lang_variant;
      EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue(
          UIA_CultureAttributeId, lang_variant.Receive()));
  
      EXPECT_EQ(lang_variant.type(), VT_I4);
      const LCID lcid = V_I4(lang_variant.ptr());
      EXPECT_EQ(LANG_FRENCH, PRIMARYLANGID(lcid));
      EXPECT_EQ(SUBLANG_FRENCH_CANADIAN, SUBLANGID(lcid));
      EXPECT_EQ(SORT_DEFAULT, SORTIDFROMLCID(lcid));
    }
  
    base::string16 font_name = base::UTF8ToUTF16("sans");
    expected_variant.Set(SysAllocString(font_name.c_str()));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontNameAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(12.0);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontSizeAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(300);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontWeightAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    // SkColor is ARGB, COLORREF is 0BGR
    expected_variant.Set(static_cast<int32_t>(0x00DEC0ADU));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
                                UIA_ForegroundColorAttributeId, expected_variant);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(document_range_provider,
                                UIA_ForegroundColorAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(false);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsHiddenAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    EXPECT_UIA_TEXTATTRIBUTE_MIXED(document_range_provider,
                                   UIA_IsHiddenAttributeId);
  
    expected_variant.Set(true);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsItalicAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(false);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
                                UIA_IsItalicAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(true);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsReadOnlyAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(false);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
                                UIA_IsReadOnlyAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(false);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(placeholder_text_range_provider,
                                UIA_IsReadOnlyAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(false);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(placeholder_text_range_provider2,
                                UIA_IsReadOnlyAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(true);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(link_text_range_provider,
                                UIA_IsReadOnlyAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(HorizontalTextAlignment_Centered);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
                                UIA_HorizontalTextAlignmentAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(HorizontalTextAlignment_Justified);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
                                UIA_HorizontalTextAlignmentAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(true);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsSubscriptAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(false);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
                                UIA_IsSubscriptAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(false);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_IsSuperscriptAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(true);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
                                UIA_IsSuperscriptAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Dot);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_OverlineStyleAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Dash);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(
        text_range_provider, UIA_StrikethroughStyleAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Single);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
                                UIA_UnderlineStyleAttributeId, expected_variant);
    expected_variant.Reset();
  
    base::string16 style_name = base::UTF8ToUTF16("");
    expected_variant.Set(SysAllocString(style_name.c_str()));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_StyleNameAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(static_cast<int32_t>(StyleId_Heading6));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(heading_text_range_provider,
                                UIA_StyleIdAttributeId, expected_variant);
    expected_variant.Reset();
  
    style_name = base::UTF8ToUTF16("mark");
    expected_variant.Set(SysAllocString(style_name.c_str()));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
                                UIA_StyleNameAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(static_cast<int32_t>(StyleId_NumberedList));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(list_item_text_range_provider,
                                UIA_StyleIdAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(static_cast<int32_t>(StyleId_BulletedList));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(list_item2_text_range_provider,
                                UIA_StyleIdAttributeId, expected_variant);
    expected_variant.Reset();
  
    expected_variant.Set(
        static_cast<int32_t>(FlowDirections::FlowDirections_RightToLeft));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(
        text_range_provider, UIA_TextFlowDirectionsAttributeId, expected_variant);
    EXPECT_UIA_TEXTATTRIBUTE_MIXED(document_range_provider,
                                   UIA_TextFlowDirectionsAttributeId);
    expected_variant.Reset();
  
    // Move the start endpoint back and forth one character to force such endpoint
    // to be located at the end of the previous anchor, this shouldn't cause
    // GetAttributeValue to include the previous anchor's attributes.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
                                     TextPatternRangeEndpoint_Start,
                                     TextUnit_Character,
                                     /*count*/ -1,
                                     /*expected_text*/ L"tmarked text",
                                     /*expected_count*/ -1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
                                     TextPatternRangeEndpoint_Start,
                                     TextUnit_Character,
                                     /*count*/ 1,
                                     /*expected_text*/ L"marked text",
                                     /*expected_count*/ 1);
    expected_variant.Set(false);
    EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
                                UIA_IsSuperscriptAttributeId, expected_variant);
    expected_variant.Reset();
  
    // Same idea as above, but moving forth and back the end endpoint to force it
    // to be located at the start of the next anchor.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
                                     TextPatternRangeEndpoint_End,
                                     TextUnit_Character,
                                     /*count*/ 1,
                                     /*expected_text*/ L"marked textl",
                                     /*expected_count*/ 1);
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
                                     TextPatternRangeEndpoint_End,
                                     TextUnit_Character,
                                     /*count*/ -1,
                                     /*expected_text*/ L"marked text",
                                     /*expected_count*/ -1);
    expected_variant.Set(
        static_cast<int32_t>(FlowDirections::FlowDirections_RightToLeft));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
                                UIA_TextFlowDirectionsAttributeId,
                                expected_variant);
    expected_variant.Reset();
  
    {
      // |text_node| has a grammar error on the entire text, and a spelling error
      // for one word, so the range has mixed annotations.
      EXPECT_UIA_TEXTATTRIBUTE_MIXED(text_range_provider,
                                     UIA_AnnotationTypesAttributeId);
  
      // start: TextPosition, anchor_id=2, text_offset=5,
      //        annotated_text=some <t>ext
      // end  : TextPosition, anchor_id=2, text_offset=9,
      //        annotated_text=some text<>
      AXPlatformNodeWin* owner =
          static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(text_node));
      ComPtr<AXPlatformNodeTextRangeProviderWin> range_with_annotations;
      CreateTextRangeProviderWin(
          range_with_annotations, owner, tree_data.tree_id,
          /*start_anchor_id=*/text_node->id(), /*start_offset=*/5,
          /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
          /*end_anchor_id=*/text_node->id(), /*end_offset=*/9,
          /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
  
      base::win::ScopedVariant annotation_types_variant;
      EXPECT_HRESULT_SUCCEEDED(range_with_annotations->GetAttributeValue(
          UIA_AnnotationTypesAttributeId, annotation_types_variant.Receive()));
  
      EXPECT_EQ(annotation_types_variant.type(), VT_ARRAY | VT_I4);
      std::vector<int> expected_annotations = {AnnotationType_SpellingError,
                                               AnnotationType_GrammarError};
      EXPECT_UIA_SAFEARRAY_EQ(V_ARRAY(annotation_types_variant.ptr()),
                              expected_annotations);
    }
  
    {
      // |heading_text_node| has a a spelling error for one word, and no
      // annotations for the remaining text, so the range has mixed annotations.
      EXPECT_UIA_TEXTATTRIBUTE_MIXED(heading_text_range_provider,
                                     UIA_AnnotationTypesAttributeId);
  
      // start: TextPosition, anchor_id=4, text_offset=5,
      //        annotated_text=more <t>ext
      // end  : TextPosition, anchor_id=4, text_offset=9,
      //        annotated_text=more text<>
      AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
          AXPlatformNodeFromNode(heading_text_node));
      ComPtr<AXPlatformNodeTextRangeProviderWin> range_with_annotations;
      CreateTextRangeProviderWin(
          range_with_annotations, owner, tree_data.tree_id,
          /*start_anchor_id=*/heading_text_node->id(), /*start_offset=*/5,
          /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
          /*end_anchor_id=*/heading_text_node->id(), /*end_offset=*/9,
          /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
  
      base::win::ScopedVariant annotation_types_variant;
      EXPECT_HRESULT_SUCCEEDED(range_with_annotations->GetAttributeValue(
          UIA_AnnotationTypesAttributeId, annotation_types_variant.Receive()));
  
      std::vector<int> expected_annotations = {AnnotationType_SpellingError};
      EXPECT_UIA_SAFEARRAY_EQ(V_ARRAY(annotation_types_variant.ptr()),
                              expected_annotations);
    }
  
    {
      base::win::ScopedVariant empty_variant;
      EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
                                  UIA_AnnotationTypesAttributeId, empty_variant);
    }
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderGetAttributeValueNotSupported) {
    ui::AXNodeData root_data;
    root_data.id = 1;
    root_data.role = ax::mojom::Role::kRootWebArea;
  
    ui::AXNodeData text_data_first;
    text_data_first.id = 2;
    text_data_first.role = ax::mojom::Role::kStaticText;
    text_data_first.SetName("first");
    root_data.child_ids.push_back(text_data_first.id);
  
    ui::AXNodeData text_data_second;
    text_data_second.id = 3;
    text_data_second.role = ax::mojom::Role::kStaticText;
    text_data_second.SetName("second");
    root_data.child_ids.push_back(text_data_second.id);
  
    ui::AXTreeUpdate update;
    ui::AXTreeData tree_data;
    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.tree_data = tree_data;
    update.has_tree_data = true;
    update.root_id = root_data.id;
    update.nodes.push_back(root_data);
    update.nodes.push_back(text_data_first);
    update.nodes.push_back(text_data_second);
  
    Init(update);
  
    ComPtr<ITextRangeProvider> document_range_provider;
    GetTextRangeProviderFromTextNode(document_range_provider, GetRootAsAXNode());
  
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_AfterParagraphSpacingAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_AnimationStyleAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_AnnotationObjectsAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_BeforeParagraphSpacingAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_CapStyleAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_CaretBidiModeAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_CaretPositionAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_IndentationFirstLineAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_IndentationLeadingAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_IndentationTrailingAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_IsActiveAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_LineSpacingAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_LinkAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_MarginBottomAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_MarginLeadingAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_MarginTopAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_MarginTrailingAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_OutlineStylesAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_OverlineColorAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_SelectionActiveEndAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_StrikethroughColorAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_TabsAttributeId);
    EXPECT_UIA_TEXTATTRIBUTE_NOTSUPPORTED(document_range_provider,
                                          UIA_UnderlineColorAttributeId);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderGetAttributeValueWithAncestorTextPosition) {
    ui::AXTreeUpdate initial_state;
    ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
    initial_state.tree_data.tree_id = tree_id;
    initial_state.has_tree_data = true;
    initial_state.root_id = 1;
    initial_state.nodes.resize(5);
    initial_state.nodes[0].id = 1;
    initial_state.nodes[0].child_ids = {2};
    initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
    initial_state.nodes[1].id = 2;
    initial_state.nodes[1].child_ids = {3};
    initial_state.nodes[1].role = ax::mojom::Role::kGenericContainer;
    initial_state.nodes[2].id = 3;
    initial_state.nodes[2].child_ids = {4, 5};
    initial_state.nodes[2].role = ax::mojom::Role::kGenericContainer;
    initial_state.nodes[3].id = 4;
    initial_state.nodes[3].role = ax::mojom::Role::kStaticText;
    initial_state.nodes[3].SetName("some text");
    initial_state.nodes[3].AddIntAttribute(
        ax::mojom::IntAttribute::kBackgroundColor, 0xDEADBEEFU);
    initial_state.nodes[4].id = 5;
    initial_state.nodes[4].role = ax::mojom::Role::kStaticText;
    initial_state.nodes[4].SetName("more text");
    initial_state.nodes[4].AddIntAttribute(
        ax::mojom::IntAttribute::kBackgroundColor, 0xDEADBEEFU);
  
    Init(initial_state);
  
    // Making |owner| AXID:2 so that |TestAXNodeWrapper::BuildAllWrappers|
    // will build the entire subtree, and not only AXID:3 for example.
    AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
        AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 2)));
  
    // start: TextPosition, anchor_id=4, text_offset=0, annotated_text=<s>ome text
    // end  : TextPosition, anchor_id=2, text_offset=17,
    //        annotated_text=some textmore tex<t>
    ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider_win;
    CreateTextRangeProviderWin(
        text_range_provider_win, owner, tree_id,
        /*start_anchor_id=*/4, /*start_offset=*/0,
        /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
        /*end_anchor_id=*/2, /*end_offset=*/17,
        /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
  
    ASSERT_TRUE(GetStart(text_range_provider_win.Get())->IsTextPosition());
    ASSERT_EQ(4, GetStart(text_range_provider_win.Get())->anchor_id());
    ASSERT_EQ(0, GetStart(text_range_provider_win.Get())->text_offset());
    ASSERT_TRUE(GetEnd(text_range_provider_win.Get())->IsTextPosition());
    ASSERT_EQ(2, GetEnd(text_range_provider_win.Get())->anchor_id());
    ASSERT_EQ(17, GetEnd(text_range_provider_win.Get())->text_offset());
  
    base::win::ScopedVariant expected_variant;
    // SkColor is ARGB, COLORREF is 0BGR
    expected_variant.Set(static_cast<int32_t>(0x00EFBEADU));
    EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider_win,
                                UIA_BackgroundColorAttributeId, expected_variant);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderSelect) {
    Init(BuildTextDocument({"some text", "more text2"}));
    AXNode* root_node = GetRootAsAXNode();
  
    // Text range for the document, which contains text "some textmore text2".
    ComPtr<IRawElementProviderSimple> root_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
    ComPtr<ITextProvider> document_provider;
    ComPtr<ITextRangeProvider> document_text_range_provider;
    ComPtr<AXPlatformNodeTextRangeProviderWin> document_text_range;
    EXPECT_HRESULT_SUCCEEDED(
        root_node_raw->GetPatternProvider(UIA_TextPatternId, &document_provider));
    EXPECT_HRESULT_SUCCEEDED(
        document_provider->get_DocumentRange(&document_text_range_provider));
    document_text_range_provider->QueryInterface(
        IID_PPV_ARGS(&document_text_range));
  
    // Text range related to "some text".
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     root_node->children()[0]);
    ComPtr<AXPlatformNodeTextRangeProviderWin> text_range;
    text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range));
  
    // Text range related to "more text2".
    ComPtr<ITextRangeProvider> more_text_range_provider;
    GetTextRangeProviderFromTextNode(more_text_range_provider,
                                     root_node->children()[1]);
    ComPtr<AXPlatformNodeTextRangeProviderWin> more_text_range;
    more_text_range_provider->QueryInterface(IID_PPV_ARGS(&more_text_range));
  
    AXPlatformNodeDelegate* delegate =
        GetOwner(document_text_range.Get())->GetDelegate();
  
    ComPtr<ITextRangeProvider> selected_text_range_provider;
    base::win::ScopedSafearray selection;
    LONG index = 0;
    LONG ubound;
    LONG lbound;
  
    // Text range "some text" performs select.
    {
      text_range_provider->Select();
  
      // Verify selection.
      AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
      EXPECT_EQ(2, unignored_selection.anchor_object_id);
      EXPECT_EQ(2, unignored_selection.focus_object_id);
      EXPECT_EQ(0, unignored_selection.anchor_offset);
      EXPECT_EQ(9, unignored_selection.focus_offset);
  
      // Verify the content of the selection.
      document_provider->GetSelection(selection.Receive());
      ASSERT_NE(nullptr, selection.Get());
  
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selection.Get(), 1, &ubound));
      EXPECT_EQ(0, ubound);
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selection.Get(), 1, &lbound));
      EXPECT_EQ(0, lbound);
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
          selection.Get(), &index,
          static_cast<void**>(&selected_text_range_provider)));
      EXPECT_UIA_TEXTRANGE_EQ(selected_text_range_provider, L"some text");
  
      selected_text_range_provider.Reset();
      selection.Reset();
    }
  
    // Text range "more text2" performs select.
    {
      more_text_range_provider->Select();
  
      // Verify selection
      AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
      EXPECT_EQ(3, unignored_selection.anchor_object_id);
      EXPECT_EQ(3, unignored_selection.focus_object_id);
      EXPECT_EQ(0, unignored_selection.anchor_offset);
      EXPECT_EQ(10, unignored_selection.focus_offset);
  
      // Verify the content of the selection.
      document_provider->GetSelection(selection.Receive());
      ASSERT_NE(nullptr, selection.Get());
  
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selection.Get(), 1, &ubound));
      EXPECT_EQ(0, ubound);
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selection.Get(), 1, &lbound));
      EXPECT_EQ(0, lbound);
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
          selection.Get(), &index,
          static_cast<void**>(&selected_text_range_provider)));
      EXPECT_UIA_TEXTRANGE_EQ(selected_text_range_provider, L"more text2");
  
      selected_text_range_provider.Reset();
      selection.Reset();
    }
  
    // Document text range "some textmore text2" performs select.
    {
      document_text_range_provider->Select();
  
      // Verify selection.
      AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
      EXPECT_EQ(2, unignored_selection.anchor_object_id);
      EXPECT_EQ(3, unignored_selection.focus_object_id);
      EXPECT_EQ(0, unignored_selection.anchor_offset);
      EXPECT_EQ(10, unignored_selection.focus_offset);
  
      // Verify the content of the selection.
      document_provider->GetSelection(selection.Receive());
      ASSERT_NE(nullptr, selection.Get());
  
      document_provider->GetSelection(selection.Receive());
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selection.Get(), 1, &ubound));
      EXPECT_EQ(0, ubound);
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selection.Get(), 1, &lbound));
      EXPECT_EQ(0, lbound);
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
          selection.Get(), &index,
          static_cast<void**>(&selected_text_range_provider)));
      EXPECT_UIA_TEXTRANGE_EQ(selected_text_range_provider,
                              L"some textmore text2");
    }
  
    // A degenerate text range performs select.
    {
      // Move the endpoint of text range so it becomes degenerate, then select.
      text_range_provider->MoveEndpointByRange(TextPatternRangeEndpoint_Start,
                                               text_range_provider.Get(),
                                               TextPatternRangeEndpoint_End);
      text_range_provider->Select();
  
      // Verify selection.
      AXTree::Selection unignored_selection = delegate->GetUnignoredSelection();
      EXPECT_EQ(2, unignored_selection.anchor_object_id);
      EXPECT_EQ(2, unignored_selection.focus_object_id);
      EXPECT_EQ(9, unignored_selection.anchor_offset);
      EXPECT_EQ(9, unignored_selection.focus_offset);
  
      // Verify selection on degenerate range.
      document_provider->GetSelection(selection.Receive());
      ASSERT_NE(nullptr, selection.Get());
  
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetUBound(selection.Get(), 1, &ubound));
      EXPECT_EQ(0, ubound);
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetLBound(selection.Get(), 1, &lbound));
      EXPECT_EQ(0, lbound);
      EXPECT_HRESULT_SUCCEEDED(SafeArrayGetElement(
          selection.Get(), &index,
          static_cast<void**>(&selected_text_range_provider)));
      EXPECT_UIA_TEXTRANGE_EQ(selected_text_range_provider, L"");
  
      selected_text_range_provider.Reset();
      selection.Reset();
    }
  }
  
  // TODO(crbug.com/1124051): Remove this test once this crbug is fixed.
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderSelectListMarker) {
    ui::AXNodeData root_data;
    root_data.id = 1;
    root_data.role = ax::mojom::Role::kRootWebArea;
  
    ui::AXNodeData list_data;
    list_data.id = 2;
    list_data.role = ax::mojom::Role::kList;
    root_data.child_ids.push_back(list_data.id);
  
    ui::AXNodeData list_item_data;
    list_item_data.id = 3;
    list_item_data.role = ax::mojom::Role::kListItem;
    list_data.child_ids.push_back(list_item_data.id);
  
    ui::AXNodeData list_marker;
    list_marker.id = 4;
    list_marker.role = ax::mojom::Role::kListMarker;
    list_item_data.child_ids.push_back(list_marker.id);
  
    ui::AXNodeData static_text_data;
    static_text_data.id = 5;
    static_text_data.role = ax::mojom::Role::kStaticText;
    static_text_data.SetName("1. ");
    list_marker.child_ids.push_back(static_text_data.id);
  
    ui::AXNodeData list_item_text_data;
    list_item_text_data.id = 6;
    list_item_text_data.role = ax::mojom::Role::kStaticText;
    list_item_text_data.SetName("First Item");
    list_item_data.child_ids.push_back(list_item_text_data.id);
  
    ui::AXTreeUpdate update;
    ui::AXTreeData tree_data;
    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.tree_data = tree_data;
    update.has_tree_data = true;
    update.root_id = root_data.id;
    update.nodes = {root_data,   list_data,        list_item_data,
                    list_marker, static_text_data, list_item_text_data};
    Init(update);
    AXNode* root_node = GetRootAsAXNode();
  
    // Text range related to "1. ".
    AXNode* list_node = root_node->children()[0];
    AXNode* list_item_node = list_node->children()[0];
    AXNode* list_marker_node = list_item_node->children()[0];
    ComPtr<ITextRangeProvider> list_marker_text_range_provider;
    GetTextRangeProviderFromTextNode(list_marker_text_range_provider,
                                     list_marker_node->children()[0]);
  
    // A list marker text range performs select.
    EXPECT_HRESULT_SUCCEEDED(list_marker_text_range_provider->Select());
  
    // Verify selection was not performed on list marker range.
    base::win::ScopedSafearray selection;
    ComPtr<IRawElementProviderSimple> root_node_raw =
        QueryInterfaceFromNode<IRawElementProviderSimple>(root_node);
    ComPtr<ITextProvider> document_provider;
    EXPECT_HRESULT_SUCCEEDED(
        root_node_raw->GetPatternProvider(UIA_TextPatternId, &document_provider));
    EXPECT_HRESULT_SUCCEEDED(
        document_provider->GetSelection(selection.Receive()));
    ASSERT_EQ(nullptr, selection.Get());
    selection.Reset();
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderFindText) {
    Init(BuildTextDocument({"some text", "more text"}));
  
    AXNode* root_node = GetRootAsAXNode();
    ComPtr<ITextRangeProvider> range;
  
    // Test Leaf kStaticText search.
    GetTextRangeProviderFromTextNode(range, root_node->children()[0]);
    EXPECT_UIA_FIND_TEXT(range, L"some text", false);
    EXPECT_UIA_FIND_TEXT(range, L"SoMe TeXt", true);
    GetTextRangeProviderFromTextNode(range, root_node->children()[1]);
    EXPECT_UIA_FIND_TEXT(range, L"more", false);
    EXPECT_UIA_FIND_TEXT(range, L"MoRe", true);
  
    // Test searching for leaf content from ancestor.
    GetTextRangeProviderFromTextNode(range, root_node);
    EXPECT_UIA_FIND_TEXT(range, L"some text", false);
    EXPECT_UIA_FIND_TEXT(range, L"SoMe TeXt", true);
    EXPECT_UIA_FIND_TEXT(range, L"more text", false);
    EXPECT_UIA_FIND_TEXT(range, L"MoRe TeXt", true);
    EXPECT_UIA_FIND_TEXT(range, L"more", false);
    // Test finding text that crosses a node boundary.
    EXPECT_UIA_FIND_TEXT(range, L"textmore", false);
    // Test no match.
    EXPECT_UIA_FIND_TEXT_NO_MATCH(range, L"no match", false);
  
    // Test if range returned is in expected anchor node.
    GetTextRangeProviderFromTextNode(range, root_node->children()[1]);
    base::win::ScopedBstr find_string(L"more text");
    Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider_found;
    EXPECT_HRESULT_SUCCEEDED(range->FindText(find_string.Get(), false, false,
                                             &text_range_provider_found));
    Microsoft::WRL::ComPtr<AXPlatformNodeTextRangeProviderWin>
        text_range_provider_win;
    text_range_provider_found->QueryInterface(
        IID_PPV_ARGS(&text_range_provider_win));
    ASSERT_TRUE(GetStart(text_range_provider_win.Get())->IsTextPosition());
    ASSERT_EQ(3, GetStart(text_range_provider_win.Get())->anchor_id());
    ASSERT_EQ(0, GetStart(text_range_provider_win.Get())->text_offset());
    ASSERT_TRUE(GetEnd(text_range_provider_win.Get())->IsTextPosition());
    ASSERT_EQ(3, GetEnd(text_range_provider_win.Get())->anchor_id());
    ASSERT_EQ(9, GetEnd(text_range_provider_win.Get())->text_offset());
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderFindTextBackwards) {
    Init(BuildTextDocument({"text", "some", "text"}));
    AXNode* root_node = GetRootAsAXNode();
  
    ComPtr<ITextRangeProvider> root_range_provider;
    GetTextRangeProviderFromTextNode(root_range_provider, root_node);
    ComPtr<ITextRangeProvider> text_node1_range;
    GetTextRangeProviderFromTextNode(text_node1_range, root_node->children()[0]);
    ComPtr<ITextRangeProvider> text_node3_range;
    GetTextRangeProviderFromTextNode(text_node3_range, root_node->children()[2]);
  
    ComPtr<ITextRangeProvider> text_range_provider_found;
    base::win::ScopedBstr find_string(L"text");
    BOOL range_equal;
  
    // Forward search finds the text_node1
    EXPECT_HRESULT_SUCCEEDED(root_range_provider->FindText(
        find_string.Get(), false, false, &text_range_provider_found));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider_found, find_string.Get());
  
    range_equal = false;
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider_found->Compare(text_node1_range.Get(), &range_equal));
    EXPECT_TRUE(range_equal);
  
    // Backwards search finds the text_node3
    EXPECT_HRESULT_SUCCEEDED(root_range_provider->FindText(
        find_string.Get(), true, false, &text_range_provider_found));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider_found, find_string.Get());
  
    range_equal = false;
    EXPECT_HRESULT_SUCCEEDED(
        text_range_provider_found->Compare(text_node3_range.Get(), &range_equal));
    EXPECT_TRUE(range_equal);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderFindAttribute) {
    // document - visible
    //  [empty]
    //
    // Search forward, look for IsHidden=true.
    // Expected: nullptr
    // Search forward, look for IsHidden=false.
    // Expected: ""
    // Note: returns "" rather than nullptr here because document root web area by
    //       default set to visible. So the text range represents document matches
    //       our searching criteria. And we return a degenerate range.
    //
    // Search backward, look for IsHidden=true.
    // Expected: nullptr
    // Search backward, look for IsHidden=false.
    // Expected: ""
    // Note: returns "" rather than nullptr here because document root web area by
    //       default set to visible. So the text range represents document matches
    //       our searching criteria. And we return a degenerate range.
    {
      ui::AXNodeData root_data;
      root_data.id = 1;
      root_data.role = ax::mojom::Role::kRootWebArea;
  
      ui::AXTreeUpdate update;
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      update.has_tree_data = true;
      update.root_id = root_data.id;
      update.nodes = {root_data};
  
      Init(update);
  
      bool is_search_backward;
      VARIANT is_hidden_attr_val;
      V_VT(&is_hidden_attr_val) = VT_BOOL;
      ComPtr<ITextRangeProvider> matched_range_provider;
      ComPtr<ITextRangeProvider> document_range_provider;
      GetTextRangeProviderFromTextNode(document_range_provider,
                                       GetRootAsAXNode());
  
      // Search forward, look for IsHidden=true.
      // Expected: nullptr
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_EQ(nullptr, matched_range_provider.Get());
  
      // Search forward, look for IsHidden=false.
      // Expected: ""
      // Note: returns "" rather than nullptr here because document root web area
      //       by default set to visible. So the text range represents document
      //       matches our searching criteria. And we return a degenerate range.
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"");
      matched_range_provider.Reset();
  
      // Search backward, look for IsHidden=true.
      // Expected: nullptr
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_EQ(nullptr, matched_range_provider.Get());
  
      // Search backward, look for IsHidden=false.
      // Expected: ""
      // Note: returns "" rather than nullptr here because document root web area
      //       by default set to visible. So the text range represents document
      //       matches our searching criteria. And we return a degenerate range.
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"");
    }
  
    // document - visible
    //  text1 - invisible
    //
    // Search forward, look for IsHidden=true.
    // Expected: "text1"
    // Search forward, look for IsHidden=false.
    // Expected: nullptr
    // Search backward, look for IsHidden=true.
    // Expected: "text1"
    // Search backward, look for IsHidden=false.
    // Expected: nullptr
    {
      ui::AXNodeData text_data1;
      text_data1.id = 2;
      text_data1.role = ax::mojom::Role::kStaticText;
      text_data1.AddState(ax::mojom::State::kInvisible);
      text_data1.SetName("text1");
  
      ui::AXNodeData root_data;
      root_data.id = 1;
      root_data.role = ax::mojom::Role::kRootWebArea;
      root_data.child_ids = {2};
  
      ui::AXTreeUpdate update;
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      update.has_tree_data = true;
      update.root_id = root_data.id;
      update.nodes = {root_data, text_data1};
  
      Init(update);
  
      bool is_search_backward;
      VARIANT is_hidden_attr_val;
      V_VT(&is_hidden_attr_val) = VT_BOOL;
      ComPtr<ITextRangeProvider> matched_range_provider;
      ComPtr<ITextRangeProvider> document_range_provider;
      GetTextRangeProviderFromTextNode(document_range_provider,
                                       GetRootAsAXNode());
  
      // Search forward, look for IsHidden=true.
      // Expected: "text1"
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1");
      matched_range_provider.Reset();
  
      // Search forward, look for IsHidden=false.
      // Expected: nullptr
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_EQ(nullptr, matched_range_provider.Get());
  
      // Search backward, look for IsHidden=true.
      // Expected: "text1"
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1");
      matched_range_provider.Reset();
  
      // Search backward, look for IsHidden=false.
      // Expected: nullptr
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_EQ(nullptr, matched_range_provider.Get());
    }
  
    // document - visible
    //  text1 - visible
    //  text2 - visible
    //
    // Search forward, look for IsHidden=true.
    // Expected: nullptr
    // Search forward, look for IsHidden=false.
    // Expected: "text1text2"
    // Search backward, look for IsHidden=true.
    // Expected: nullptr
    // Search backward, look for IsHidden=false.
    // Expected: "text1text2"
    {
      ui::AXNodeData text_data1;
      text_data1.id = 2;
      text_data1.role = ax::mojom::Role::kStaticText;
      text_data1.SetName("text1");
  
      ui::AXNodeData text_data2;
      text_data2.id = 3;
      text_data2.role = ax::mojom::Role::kStaticText;
      text_data2.SetName("text2");
  
      ui::AXNodeData root_data;
      root_data.id = 1;
      root_data.role = ax::mojom::Role::kRootWebArea;
      root_data.child_ids = {2, 3};
  
      ui::AXTreeUpdate update;
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      update.has_tree_data = true;
      update.root_id = root_data.id;
      update.nodes = {root_data, text_data1, text_data2};
  
      Init(update);
  
      bool is_search_backward;
      VARIANT is_hidden_attr_val;
      V_VT(&is_hidden_attr_val) = VT_BOOL;
      ComPtr<ITextRangeProvider> matched_range_provider;
      ComPtr<ITextRangeProvider> document_range_provider;
      GetTextRangeProviderFromTextNode(document_range_provider,
                                       GetRootAsAXNode());
  
      // Search forward, look for IsHidden=true.
      // Expected: nullptr
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_EQ(nullptr, matched_range_provider.Get());
  
      // Search forward, look for IsHidden=false.
      // Expected: "text1text2"
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1text2");
      matched_range_provider.Reset();
  
      // Search backward, look for IsHidden=true.
      // Expected: nullptr
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_EQ(nullptr, matched_range_provider.Get());
  
      // Search backward, look for IsHidden=false.
      // Expected: "text1text2"
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1text2");
    }
  
    // document - visible
    //  text1 - visible
    //  text2 - invisible
    //  text3 - invisible
    //  text4 - visible
    //  text5 - invisible
    //
    // Search forward, look for IsHidden=true.
    // Expected: "text2text3"
    // Search forward, look for IsHidden=false.
    // Expected: "text1"
    // Search backward, look for IsHidden=true.
    // Expected: "text5"
    // Search backward, look for IsHidden=false.
    // Expected: "text4"
    {
      ui::AXNodeData text_data1;
      text_data1.id = 2;
      text_data1.role = ax::mojom::Role::kStaticText;
      text_data1.SetName("text1");
  
      ui::AXNodeData text_data2;
      text_data2.id = 3;
      text_data2.role = ax::mojom::Role::kStaticText;
      text_data2.AddState(ax::mojom::State::kInvisible);
      text_data2.SetName("text2");
  
      ui::AXNodeData text_data3;
      text_data3.id = 4;
      text_data3.role = ax::mojom::Role::kStaticText;
      text_data3.AddState(ax::mojom::State::kInvisible);
      text_data3.SetName("text3");
  
      ui::AXNodeData text_data4;
      text_data4.id = 5;
      text_data4.role = ax::mojom::Role::kStaticText;
      text_data4.SetName("text4");
  
      ui::AXNodeData text_data5;
      text_data5.id = 6;
      text_data5.role = ax::mojom::Role::kStaticText;
      text_data5.AddState(ax::mojom::State::kInvisible);
      text_data5.SetName("text5");
  
      ui::AXNodeData root_data;
      root_data.id = 1;
      root_data.role = ax::mojom::Role::kRootWebArea;
      root_data.child_ids = {2, 3, 4, 5, 6};
  
      ui::AXTreeUpdate update;
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      update.has_tree_data = true;
      update.root_id = root_data.id;
      update.nodes = {root_data,  text_data1, text_data2,
                      text_data3, text_data4, text_data5};
  
      Init(update);
  
      bool is_search_backward;
      VARIANT is_hidden_attr_val;
      V_VT(&is_hidden_attr_val) = VT_BOOL;
      ComPtr<ITextRangeProvider> matched_range_provider;
      ComPtr<ITextRangeProvider> document_range_provider;
      GetTextRangeProviderFromTextNode(document_range_provider,
                                       GetRootAsAXNode());
  
      // Search forward, look for IsHidden=true.
      // Expected: "text2text3"
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text2text3");
      matched_range_provider.Reset();
  
      // Search forward, look for IsHidden=false.
      // Expected: "text1"
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1");
      matched_range_provider.Reset();
  
      // Search backward, look for IsHidden=true.
      // Expected: "text5"
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text5");
      matched_range_provider.Reset();
  
      // Search backward, look for IsHidden=false.
      // Expected: "text4"
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text4");
    }
  
    // document - visible
    //  text1 - visible
    //  text2 - invisible
    //  text3 - invisible
    //  text4 - invisible
    //  text5 - visible
    //
    // Search forward, look for IsHidden=true.
    // Expected: "text2text3text4"
    // Search forward, look for IsHidden=false.
    // Expected: "text1"
    // Search backward, look for IsHidden=true.
    // Expected: "text2text3text4"
    // Search backward, look for IsHidden=false.
    // Expected: "text5"
    {
      ui::AXNodeData text_data1;
      text_data1.id = 2;
      text_data1.role = ax::mojom::Role::kStaticText;
      text_data1.SetName("text1");
  
      ui::AXNodeData text_data2;
      text_data2.id = 3;
      text_data2.role = ax::mojom::Role::kStaticText;
      text_data2.AddState(ax::mojom::State::kInvisible);
      text_data2.SetName("text2");
  
      ui::AXNodeData text_data3;
      text_data3.id = 4;
      text_data3.role = ax::mojom::Role::kStaticText;
      text_data3.AddState(ax::mojom::State::kInvisible);
      text_data3.SetName("text3");
  
      ui::AXNodeData text_data4;
      text_data4.id = 5;
      text_data4.role = ax::mojom::Role::kStaticText;
      text_data4.AddState(ax::mojom::State::kInvisible);
      text_data4.SetName("text4");
  
      ui::AXNodeData text_data5;
      text_data5.id = 6;
      text_data5.role = ax::mojom::Role::kStaticText;
      text_data5.SetName("text5");
  
      ui::AXNodeData root_data;
      root_data.id = 1;
      root_data.role = ax::mojom::Role::kRootWebArea;
      root_data.child_ids = {2, 3, 4, 5, 6};
  
      ui::AXTreeUpdate update;
      update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
      update.has_tree_data = true;
      update.root_id = root_data.id;
      update.nodes = {root_data,  text_data1, text_data2,
                      text_data3, text_data4, text_data5};
  
      Init(update);
  
      bool is_search_backward;
      VARIANT is_hidden_attr_val;
      V_VT(&is_hidden_attr_val) = VT_BOOL;
      ComPtr<ITextRangeProvider> matched_range_provider;
      ComPtr<ITextRangeProvider> document_range_provider;
      GetTextRangeProviderFromTextNode(document_range_provider,
                                       GetRootAsAXNode());
  
      // Search forward, look for IsHidden=true.
      // Expected: "text2text3text4"
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text2text3text4");
      matched_range_provider.Reset();
  
      // Search forward, look for IsHidden=false.
      // Expected: "text1"
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = false;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text1");
      matched_range_provider.Reset();
  
      // Search backward, look for IsHidden=true.
      // Expected: "text2text3text4"
      V_BOOL(&is_hidden_attr_val) = VARIANT_TRUE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text2text3text4");
      matched_range_provider.Reset();
  
      // Search backward, look for IsHidden=false.
      // Expected: "text5"
      V_BOOL(&is_hidden_attr_val) = VARIANT_FALSE;
      is_search_backward = true;
      document_range_provider->FindAttribute(
          UIA_IsHiddenAttributeId, is_hidden_attr_val, is_search_backward,
          &matched_range_provider);
      ASSERT_NE(nullptr, matched_range_provider.Get());
      EXPECT_UIA_TEXTRANGE_EQ(matched_range_provider, L"text5");
    }
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, ElementNotAvailable) {
    AXNodeData root_ax_node_data;
    root_ax_node_data.id = 1;
    root_ax_node_data.role = ax::mojom::Role::kRootWebArea;
  
    Init(root_ax_node_data);
  
    ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
        QueryInterfaceFromNode<IRawElementProviderSimple>(GetRootAsAXNode());
    ASSERT_NE(nullptr, raw_element_provider_simple.Get());
  
    ComPtr<ITextProvider> text_provider;
    ASSERT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
        UIA_TextPatternId, &text_provider));
    ASSERT_NE(nullptr, text_provider.Get());
  
    ComPtr<ITextRangeProvider> text_range_provider;
    ASSERT_HRESULT_SUCCEEDED(
        text_provider->get_DocumentRange(&text_range_provider));
    ASSERT_NE(nullptr, text_range_provider.Get());
  
    // An empty tree.
    SetTree(std::make_unique<AXTree>());
  
    BOOL bool_arg = FALSE;
    ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
              text_range_provider->ScrollIntoView(bool_arg));
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestITextRangeProviderIgnoredNodes) {
    // Parent Tree
    // 1
    // |
    // 2(i)
    // |________________________________
    // |   |   |    |      |           |
    // 3   4   5    6      7(i)        8(i)
    //              |      |________
    //              |      |       |
    //              9(i)   10(i)   11
    //              |      |____
    //              |      |   |
    //              12    13   14
  
    ui::AXTreeUpdate tree_update;
    ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
    tree_update.tree_data.tree_id = tree_id;
    tree_update.has_tree_data = true;
    tree_update.root_id = 1;
    tree_update.nodes.resize(14);
    tree_update.nodes[0].id = 1;
    tree_update.nodes[0].child_ids = {2};
    tree_update.nodes[0].role = ax::mojom::Role::kRootWebArea;
  
    tree_update.nodes[1].id = 2;
    tree_update.nodes[1].child_ids = {3, 4, 5, 6, 7, 8};
    tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
    tree_update.nodes[1].role = ax::mojom::Role::kDocument;
  
    tree_update.nodes[2].id = 3;
    tree_update.nodes[2].role = ax::mojom::Role::kStaticText;
    tree_update.nodes[2].SetName(".3.");
  
    tree_update.nodes[3].id = 4;
    tree_update.nodes[3].role = ax::mojom::Role::kStaticText;
    tree_update.nodes[3].SetName(".4.");
  
    tree_update.nodes[4].id = 5;
    tree_update.nodes[4].role = ax::mojom::Role::kStaticText;
    tree_update.nodes[4].SetName(".5.");
  
    tree_update.nodes[5].id = 6;
    tree_update.nodes[5].role = ax::mojom::Role::kGenericContainer;
    tree_update.nodes[5].child_ids = {9};
  
    tree_update.nodes[6].id = 7;
    tree_update.nodes[6].child_ids = {10, 11};
    tree_update.nodes[6].AddState(ax::mojom::State::kIgnored);
    tree_update.nodes[6].role = ax::mojom::Role::kGenericContainer;
  
    tree_update.nodes[7].id = 8;
    tree_update.nodes[7].AddState(ax::mojom::State::kIgnored);
    tree_update.nodes[7].role = ax::mojom::Role::kStaticText;
    tree_update.nodes[7].SetName(".8.");
  
    tree_update.nodes[8].id = 9;
    tree_update.nodes[8].child_ids = {12};
    tree_update.nodes[8].AddState(ax::mojom::State::kIgnored);
    tree_update.nodes[8].role = ax::mojom::Role::kGenericContainer;
  
    tree_update.nodes[9].id = 10;
    tree_update.nodes[9].child_ids = {13, 14};
    tree_update.nodes[9].AddState(ax::mojom::State::kIgnored);
    tree_update.nodes[8].role = ax::mojom::Role::kGenericContainer;
  
    tree_update.nodes[10].id = 11;
    tree_update.nodes[10].role = ax::mojom::Role::kStaticText;
    tree_update.nodes[10].SetName(".11.");
  
    tree_update.nodes[11].id = 12;
    tree_update.nodes[11].role = ax::mojom::Role::kStaticText;
    tree_update.nodes[11].AddState(ax::mojom::State::kIgnored);
    tree_update.nodes[11].SetName(".12.");
  
    tree_update.nodes[12].id = 13;
    tree_update.nodes[12].role = ax::mojom::Role::kStaticText;
    tree_update.nodes[12].SetName(".13.");
  
    tree_update.nodes[13].id = 14;
    tree_update.nodes[13].role = ax::mojom::Role::kStaticText;
    tree_update.nodes[13].SetName(".14.");
  
    Init(tree_update);
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 1),
                             GetNodeFromTree(tree_id, 1));
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 2),
                             GetNodeFromTree(tree_id, 1));
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 3),
                             GetNodeFromTree(tree_id, 3));
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 4),
                             GetNodeFromTree(tree_id, 4));
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 5),
                             GetNodeFromTree(tree_id, 5));
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 8),
                             GetNodeFromTree(tree_id, 1));
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 11),
                             GetNodeFromTree(tree_id, 11));
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 13),
                             GetNodeFromTree(tree_id, 13));
    EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 14),
                             GetNodeFromTree(tree_id, 14));
  
    // Test movement and GetText()
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetNodeFromTree(tree_id, 1));
  
    ASSERT_HRESULT_SUCCEEDED(
        text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L".");
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 2,
        /*expected_text*/ L".3.",
        /*expected_count*/ 2);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 6,
        /*expected_text*/ L".3..4..5.",
        /*expected_count*/ 6);
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 13,
        /*expected_text*/ L".3..4..5.\xFFFC.13..14..11.",
        /*expected_count*/ 13);
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestNormalizeTextRangePastEndOfDocument) {
    ui::AXTreeUpdate initial_state;
    ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
    initial_state.tree_data.tree_id = tree_id;
    initial_state.has_tree_data = true;
    initial_state.root_id = 1;
    initial_state.nodes.resize(3);
    initial_state.nodes[0].id = 1;
    initial_state.nodes[0].child_ids = {2};
    initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
    initial_state.nodes[1].id = 2;
    initial_state.nodes[1].child_ids = {3};
    initial_state.nodes[1].role = ax::mojom::Role::kStaticText;
    initial_state.nodes[1].SetName("aaa");
    initial_state.nodes[2].id = 3;
    initial_state.nodes[2].role = ax::mojom::Role::kInlineTextBox;
    initial_state.nodes[2].SetName("aaa");
  
    Init(initial_state);
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetNodeFromTree(tree_id, 3));
  
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"aaa");
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 2,
        /*expected_text*/ L"a",
        /*expected_count*/ 2);
  
    ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider_win;
    text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range_provider_win));
  
    const AXNodePosition::AXPositionInstance start_after_move =
        GetStart(text_range_provider_win.Get())->Clone();
    const AXNodePosition::AXPositionInstance end_after_move =
        GetEnd(text_range_provider_win.Get())->Clone();
    EXPECT_LT(*start_after_move, *end_after_move);
  
    AXTreeUpdate update;
    update.nodes.resize(2);
    update.nodes[0] = initial_state.nodes[1];
    update.nodes[0].SetName("aa");
    update.nodes[1] = initial_state.nodes[2];
    update.nodes[1].SetName("aa");
    ASSERT_TRUE(GetTree()->Unserialize(update));
  
    NormalizeTextRange(text_range_provider_win.Get());
    EXPECT_EQ(*start_after_move, *GetStart(text_range_provider_win.Get()));
    EXPECT_EQ(*end_after_move, *GetEnd(text_range_provider_win.Get()));
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestNormalizeTextRangePastEndOfDocumentWithIgnoredNodes) {
    ui::AXTreeUpdate initial_state;
    ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
    initial_state.tree_data.tree_id = tree_id;
    initial_state.has_tree_data = true;
    initial_state.root_id = 1;
    initial_state.nodes.resize(4);
    initial_state.nodes[0].id = 1;
    initial_state.nodes[0].child_ids = {2};
    initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
    initial_state.nodes[1].id = 2;
    initial_state.nodes[1].child_ids = {3, 4};
    initial_state.nodes[1].role = ax::mojom::Role::kStaticText;
    initial_state.nodes[1].SetName("aaa");
    initial_state.nodes[2].id = 3;
    initial_state.nodes[2].role = ax::mojom::Role::kInlineTextBox;
    initial_state.nodes[2].SetName("aaa");
    initial_state.nodes[3].id = 4;
    initial_state.nodes[3].role = ax::mojom::Role::kInlineTextBox;
    initial_state.nodes[3].AddState(ax::mojom::State::kIgnored);
    initial_state.nodes[3].SetName("ignored");
  
    Init(initial_state);
  
    ComPtr<ITextRangeProvider> text_range_provider;
    GetTextRangeProviderFromTextNode(text_range_provider,
                                     GetNodeFromTree(tree_id, 3));
  
    EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"aaa");
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 2,
        /*expected_text*/ L"a",
        /*expected_count*/ 2);
  
    ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider_win;
    text_range_provider->QueryInterface(IID_PPV_ARGS(&text_range_provider_win));
  
    const AXNodePosition::AXPositionInstance start_after_move =
        GetStart(text_range_provider_win.Get())->Clone();
    const AXNodePosition::AXPositionInstance end_after_move =
        GetEnd(text_range_provider_win.Get())->Clone();
    EXPECT_LT(*start_after_move, *end_after_move);
  
    AXTreeUpdate update;
    update.nodes.resize(2);
    update.nodes[0] = initial_state.nodes[1];
    update.nodes[0].SetName("aa");
    update.nodes[1] = initial_state.nodes[2];
    update.nodes[1].SetName("aa");
    ASSERT_TRUE(GetTree()->Unserialize(update));
  
    NormalizeTextRange(text_range_provider_win.Get());
    EXPECT_EQ(*start_after_move, *GetStart(text_range_provider_win.Get()));
    EXPECT_EQ(*end_after_move, *GetEnd(text_range_provider_win.Get()));
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestNormalizeTextRangeInsideIgnoredNodes) {
    ui::AXTreeUpdate initial_state;
    ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
    initial_state.tree_data.tree_id = tree_id;
    initial_state.has_tree_data = true;
    initial_state.root_id = 1;
    initial_state.nodes.resize(4);
    initial_state.nodes[0].id = 1;
    initial_state.nodes[0].child_ids = {2, 3, 4};
    initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea;
    initial_state.nodes[1].id = 2;
    initial_state.nodes[1].role = ax::mojom::Role::kStaticText;
    initial_state.nodes[1].SetName("before");
    initial_state.nodes[2].id = 3;
    initial_state.nodes[2].role = ax::mojom::Role::kStaticText;
    initial_state.nodes[2].AddState(ax::mojom::State::kIgnored);
    initial_state.nodes[2].SetName("ignored");
    initial_state.nodes[3].id = 4;
    initial_state.nodes[3].role = ax::mojom::Role::kStaticText;
    initial_state.nodes[3].SetName("after");
  
    Init(initial_state);
  
    // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
    // will build the entire tree.
    AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
        AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
  
    // start: TextPosition, anchor_id=3, text_offset=1, annotated_text=i<g>nored
    // end  : TextPosition, anchor_id=3, text_offset=6, annotated_text=ignore<d>
    ComPtr<AXPlatformNodeTextRangeProviderWin> ignored_range_win;
    CreateTextRangeProviderWin(
        ignored_range_win, owner, tree_id,
        /*start_anchor_id=*/3, /*start_offset=*/1,
        /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
        /*end_anchor_id=*/3, /*end_offset=*/6,
        /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
  
    EXPECT_TRUE(GetStart(ignored_range_win.Get())->IsIgnored());
    EXPECT_TRUE(GetEnd(ignored_range_win.Get())->IsIgnored());
  
    ComPtr<AXPlatformNodeTextRangeProviderWin> normalized_range_win =
        CloneTextRangeProviderWin(ignored_range_win.Get());
    NormalizeTextRange(normalized_range_win.Get());
  
    EXPECT_FALSE(GetStart(normalized_range_win.Get())->IsIgnored());
    EXPECT_FALSE(GetEnd(normalized_range_win.Get())->IsIgnored());
    EXPECT_LE(*GetStart(ignored_range_win.Get()),
              *GetStart(normalized_range_win.Get()));
    EXPECT_LE(*GetEnd(ignored_range_win.Get()),
              *GetEnd(normalized_range_win.Get()));
    EXPECT_LE(*GetStart(normalized_range_win.Get()),
              *GetEnd(normalized_range_win.Get()));
  
    // Remove the last node, forcing |NormalizeTextRange| to normalize
    // using the opposite AdjustmentBehavior.
    AXTreeUpdate update;
    update.nodes.resize(1);
    update.nodes[0] = initial_state.nodes[0];
    update.nodes[0].child_ids = {2, 3};
    ASSERT_TRUE(GetTree()->Unserialize(update));
  
    normalized_range_win = CloneTextRangeProviderWin(ignored_range_win.Get());
    NormalizeTextRange(normalized_range_win.Get());
  
    EXPECT_FALSE(GetStart(normalized_range_win.Get())->IsIgnored());
    EXPECT_FALSE(GetEnd(normalized_range_win.Get())->IsIgnored());
    EXPECT_GE(*GetStart(ignored_range_win.Get()),
              *GetStart(normalized_range_win.Get()));
    EXPECT_GE(*GetEnd(ignored_range_win.Get()),
              *GetEnd(normalized_range_win.Get()));
    EXPECT_LE(*GetStart(normalized_range_win.Get()),
              *GetEnd(normalized_range_win.Get()));
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestNormalizeTextRangeSpanIgnoredNodes) {
    ui::AXNodeData root_data;
    root_data.id = 1;
    root_data.role = ax::mojom::Role::kRootWebArea;
  
    ui::AXNodeData before_text;
    before_text.id = 2;
    before_text.role = ax::mojom::Role::kStaticText;
    before_text.SetName("before");
    root_data.child_ids.push_back(before_text.id);
  
    ui::AXNodeData ignored_text1;
    ignored_text1.id = 3;
    ignored_text1.role = ax::mojom::Role::kStaticText;
    ignored_text1.AddState(ax::mojom::State::kIgnored);
    ignored_text1.SetName("ignored1");
    root_data.child_ids.push_back(ignored_text1.id);
  
    ui::AXNodeData ignored_text2;
    ignored_text2.id = 4;
    ignored_text2.role = ax::mojom::Role::kStaticText;
    ignored_text2.AddState(ax::mojom::State::kIgnored);
    ignored_text2.SetName("ignored2");
    root_data.child_ids.push_back(ignored_text2.id);
  
    ui::AXNodeData after_text;
    after_text.id = 5;
    after_text.role = ax::mojom::Role::kStaticText;
    after_text.SetName("after");
    root_data.child_ids.push_back(after_text.id);
  
    ui::AXTreeUpdate update;
    ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.root_id = root_data.id;
    update.tree_data.tree_id = tree_id;
    update.has_tree_data = true;
    update.nodes = {root_data, before_text, ignored_text1, ignored_text2,
                    after_text};
    Init(update);
  
    // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
    // will build the entire tree.
    AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
        AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
  
    // Text range before NormalizeTextRange()
    // |before<>||ignored1||ignored2||<a>fter|
    //         |-----------------------|
    // start: TextPosition, anchor_id=2, text_offset=6, annotated_text=before<>
    // end  : TextPosition, anchor_id=5, text_offset=0, annotated_text=<a>fter
    ComPtr<AXPlatformNodeTextRangeProviderWin> range_span_ignored_nodes;
    CreateTextRangeProviderWin(
        range_span_ignored_nodes, owner, tree_id,
        /*start_anchor_id=*/2, /*start_offset=*/6,
        /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
        /*end_anchor_id=*/5, /*end_offset=*/0,
        /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
  
    // Text range after NormalizeTextRange()
    // |before||ignored1||ignored2||<a>fter|
    //                              |-|
    NormalizeTextRange(range_span_ignored_nodes.Get());
    EXPECT_EQ(*GetStart(range_span_ignored_nodes.Get()),
              *GetEnd(range_span_ignored_nodes.Get()));
  
    EXPECT_EQ(true, GetStart(range_span_ignored_nodes.Get())->IsTextPosition());
    EXPECT_EQ(true, GetStart(range_span_ignored_nodes.Get())->AtStartOfAnchor());
    EXPECT_EQ(5, GetStart(range_span_ignored_nodes.Get())->anchor_id());
    EXPECT_EQ(0, GetStart(range_span_ignored_nodes.Get())->text_offset());
  
    EXPECT_EQ(true, GetEnd(range_span_ignored_nodes.Get())->IsTextPosition());
    EXPECT_EQ(true, GetEnd(range_span_ignored_nodes.Get())->AtStartOfAnchor());
    EXPECT_EQ(5, GetEnd(range_span_ignored_nodes.Get())->anchor_id());
    EXPECT_EQ(0, GetEnd(range_span_ignored_nodes.Get())->text_offset());
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest,
         TestNormalizeTextRangeForceSameAnchorOnDegenerateRange) {
    // ++1 kRootWebArea
    // ++++2 kGenericContainer
    // ++++++3 kImage
    // ++++4 kTextField
    // ++++++5 kGenericContainer
    // ++++++++6 kStaticText
    // ++++++++++7 kInlineTextBox
    ui::AXNodeData root_1;
    ui::AXNodeData generic_container_2;
    ui::AXNodeData image_3;
    ui::AXNodeData text_field_4;
    ui::AXNodeData generic_container_5;
    ui::AXNodeData static_text_6;
    ui::AXNodeData inline_box_7;
  
    root_1.id = 1;
    generic_container_2.id = 2;
    image_3.id = 3;
    text_field_4.id = 4;
    generic_container_5.id = 5;
    static_text_6.id = 6;
    inline_box_7.id = 7;
  
    root_1.role = ax::mojom::Role::kRootWebArea;
    root_1.child_ids = {generic_container_2.id, text_field_4.id};
  
    generic_container_2.role = ax::mojom::Role::kGenericContainer;
    generic_container_2.AddBoolAttribute(
        ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
    generic_container_2.child_ids = {image_3.id};
  
    image_3.role = ax::mojom::Role::kImage;
  
    text_field_4.role = ax::mojom::Role::kTextField;
    text_field_4.child_ids = {generic_container_5.id};
    text_field_4.SetValue("3.14");
  
    generic_container_5.role = ax::mojom::Role::kGenericContainer;
    generic_container_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("3.14");
  
    inline_box_7.role = ax::mojom::Role::kInlineTextBox;
    inline_box_7.SetName("3.14");
  
    ui::AXTreeUpdate update;
    ui::AXTreeData tree_data;
    tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.tree_data = tree_data;
    update.has_tree_data = true;
    update.root_id = root_1.id;
    update.nodes.push_back(root_1);
    update.nodes.push_back(generic_container_2);
    update.nodes.push_back(image_3);
    update.nodes.push_back(text_field_4);
    update.nodes.push_back(generic_container_5);
    update.nodes.push_back(static_text_6);
    update.nodes.push_back(inline_box_7);
  
    Init(update);
  
    AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
        AXPlatformNodeFromNode(GetNodeFromTree(tree_data.tree_id, 1)));
  
    // start: TextPosition, anchor_id=3, text_offset=1, annotated_text=/xFFFC<>
    // end  : TextPosition, anchor_id=7, text_offset=0, annotated_text=<p>i
    ComPtr<AXPlatformNodeTextRangeProviderWin> range;
    CreateTextRangeProviderWin(
        range, owner, tree_data.tree_id,
        /*start_anchor_id=*/3, /*start_offset=*/1,
        /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
        /*end_anchor_id=*/7, /*end_offset=*/0,
        /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
  
    NormalizeTextRange(range.Get());
    EXPECT_EQ(*GetStart(range.Get()), *GetEnd(range.Get()));
  
    EXPECT_EQ(true, GetStart(range.Get())->AtStartOfAnchor());
    EXPECT_EQ(true, GetEnd(range.Get())->AtStartOfAnchor());
    EXPECT_EQ(7, GetStart(range.Get())->anchor_id());
    EXPECT_EQ(7, GetEnd(range.Get())->anchor_id());
  }
  
  TEST_F(AXPlatformNodeTextRangeProviderTest, TestValidateStartAndEnd) {
    // 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};
  
    ui::AXTreeUpdate update;
    ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
    update.root_id = root_data.id;
    update.tree_data.tree_id = tree_id;
    update.has_tree_data = true;
    update.nodes = {root_data, text_data, more_text_data};
    Init(update);
  
    // Create a position at MaxTextOffset
    // Making |owner| AXID:1 so that |TestAXNodeWrapper::BuildAllWrappers|
    // will build the entire tree.
    AXPlatformNodeWin* owner = static_cast<AXPlatformNodeWin*>(
        AXPlatformNodeFromNode(GetNodeFromTree(tree_id, 1)));
  
    // start: TextPosition, anchor_id=1, text_offset=0, annotated_text=<s>ome text
    // end  : TextPosition, anchor_id=3, text_offset=9, annotated_text=more text<>
    ComPtr<AXPlatformNodeTextRangeProviderWin> text_range_provider;
    CreateTextRangeProviderWin(
        text_range_provider, owner, tree_id,
        /*start_anchor_id=*/1, /*start_offset=*/0,
        /*start_affinity*/ ax::mojom::TextAffinity::kDownstream,
        /*end_anchor_id=*/3, /*end_offset=*/9,
        /*end_affinity*/ ax::mojom::TextAffinity::kDownstream);
  
    // Since the end of the range is at MaxTextOffset, moving it by 1 character
    // should have an expected_count of 0.
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/ L"some textmore text",
        /*expected_count*/ 0);
  
    // Now make a change to shorten MaxTextOffset. Ensure that this position is
    // invalid, then call SnapToMaxTextOffsetIfBeyond and ensure that it is now
    // valid.
    more_text_data.SetName("ore tex");
    AXTreeUpdate test_update;
    test_update.nodes = {more_text_data};
    ASSERT_TRUE(GetTree()->Unserialize(test_update));
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/ L"some textore tex",
        /*expected_count*/ 0);
  
    // Now modify the tree so that start_ is pointing to a node that has been
    // removed from the tree.
    text_data.SetName("");
    AXTreeUpdate test_update2;
    test_update2.nodes = {text_data};
    ASSERT_TRUE(GetTree()->Unserialize(test_update2));
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/ L"re tex",
        /*expected_count*/ 1);
  
    // Now adjust a node that's not the final node in the tree to point past
    // MaxTextOffset. First move the range endpoints so that they're pointing to
    // MaxTextOffset on the first node.
    text_data.SetName("some text");
    AXTreeUpdate test_update3;
    test_update3.nodes = {text_data};
    ASSERT_TRUE(GetTree()->Unserialize(test_update3));
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ -10,
        /*expected_text*/ L"some textore tex",
        /*expected_count*/ -10);
  
    // Ensure that we're at MaxTextOffset on the first node by first
    // overshooting a negative move...
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ -8,
        /*expected_text*/ L"some tex",
        /*expected_count*/ -8);
  
    // ...followed by a positive move
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/ L"some text",
        /*expected_count*/ 1);
  
    // Now our range's start_ is pointing to offset 0 on the first node and end_
    // is pointing to MaxTextOffset on the first node. Now modify the tree so
    // that MaxTextOffset is invalid on the first node and ensure that we can
    // still move
    text_data.SetName("some tex");
    AXTreeUpdate test_update4;
    test_update4.nodes = {text_data};
    ASSERT_TRUE(GetTree()->Unserialize(test_update4));
  
    EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
        text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Character,
        /*count*/ 1,
        /*expected_text*/ L"ome tex",
        /*expected_count*/ 1);
  }
  }  // namespace ui
  