blob: d36a8cc9d81cbc64a43c606755b70711d77419d0 [file] [log] [blame]
// 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,
AXNodePosition::AXPositionInstance& start,
AXNodePosition::AXPositionInstance& end) {
DCHECK_EQ(*GetStart(text_range), *start);
DCHECK_EQ(*GetEnd(text_range), *end);
text_range->NormalizeTextRange(start, end);
}
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;
}
void ExpectPositionsEqual(const AXNodePosition::AXPositionInstance& a,
const AXNodePosition::AXPositionInstance& b) {
EXPECT_EQ(*a, *b);
EXPECT_EQ(a->anchor_id(), b->anchor_id());
EXPECT_EQ(a->text_offset(), b->text_offset());
}
};
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,