1989 lines
79 KiB
C++
1989 lines
79 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
#include "WexTestClass.h"
|
|
#include "../../inc/consoletaeftemplates.hpp"
|
|
#include "CommonState.hpp"
|
|
|
|
#include "uiaTextRange.hpp"
|
|
#include "../types/ScreenInfoUiaProviderBase.h"
|
|
#include "../../../buffer/out/textBuffer.hpp"
|
|
#include "../types/UiaTracing.h"
|
|
|
|
#include <IDataSource.h>
|
|
|
|
using namespace WEX::Common;
|
|
using namespace WEX::Logging;
|
|
using namespace WEX::TestExecution;
|
|
|
|
using namespace Microsoft::WRL;
|
|
|
|
using namespace Microsoft::Console::Interactivity::Win32;
|
|
|
|
static constexpr til::point point_offset_by_char(const til::point start, const til::rectangle bounds, ptrdiff_t amt)
|
|
{
|
|
ptrdiff_t pos_x = start.x();
|
|
ptrdiff_t pos_y = start.y();
|
|
while (amt != 0)
|
|
{
|
|
if (amt > 0)
|
|
{
|
|
if (pos_x == bounds.left() && pos_y == bounds.bottom())
|
|
{
|
|
// end exclusive --> can't move any more
|
|
break;
|
|
}
|
|
else if (pos_x == bounds.right() - 1)
|
|
{
|
|
// right boundary --> wrap
|
|
pos_x = bounds.left();
|
|
++pos_y;
|
|
}
|
|
else
|
|
{
|
|
// standard move
|
|
++pos_x;
|
|
}
|
|
--amt;
|
|
}
|
|
else
|
|
{
|
|
if (pos_x == bounds.left() && pos_y == bounds.top())
|
|
{
|
|
// origin --> can't move any more
|
|
break;
|
|
}
|
|
else if (pos_x == bounds.left())
|
|
{
|
|
// left boundary --> wrap
|
|
pos_x = bounds.right() - 1;
|
|
--pos_y;
|
|
}
|
|
else
|
|
{
|
|
// standard move
|
|
--pos_x;
|
|
}
|
|
++amt;
|
|
}
|
|
}
|
|
return { pos_x, pos_y };
|
|
}
|
|
|
|
static constexpr til::point point_offset_by_line(const til::point start, const til::rectangle bounds, ptrdiff_t amt)
|
|
{
|
|
// X = left boundary for UIA
|
|
ptrdiff_t pos_x = bounds.left();
|
|
ptrdiff_t pos_y = start.y();
|
|
while (amt != 0)
|
|
{
|
|
if (amt > 0)
|
|
{
|
|
if (pos_y == bounds.bottom() + 1)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
++pos_y;
|
|
}
|
|
--amt;
|
|
}
|
|
else
|
|
{
|
|
if (pos_y == bounds.top())
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
--pos_y;
|
|
}
|
|
++amt;
|
|
}
|
|
}
|
|
return { pos_x, pos_y };
|
|
}
|
|
|
|
// IMPORTANT: reference this _after_ defining point_offset_by_XXX. We need it for some definitions
|
|
#include "GeneratedUiaTextRangeMovementTests.g.cpp"
|
|
|
|
namespace
|
|
{
|
|
#pragma region TAEF hookup for the test case array above
|
|
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
|
|
{
|
|
HRESULT RuntimeClassInitialize(const size_t index)
|
|
{
|
|
_index = index;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
|
{
|
|
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
|
|
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
|
LONG index{ 0 };
|
|
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
|
|
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
|
|
*ppData = safeArray;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
|
|
{
|
|
*ppMetadataNames = nullptr;
|
|
return S_FALSE;
|
|
}
|
|
|
|
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
|
{
|
|
*ppData = nullptr;
|
|
return S_FALSE;
|
|
}
|
|
|
|
STDMETHODIMP GetName(BSTR* ppszRowName) override
|
|
{
|
|
*ppszRowName = wil::make_bstr(s_movementTests[_index].name.data()).release();
|
|
return S_OK;
|
|
}
|
|
|
|
private:
|
|
size_t _index;
|
|
};
|
|
|
|
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
|
|
{
|
|
STDMETHODIMP Advance(IDataRow** ppDataRow) override
|
|
{
|
|
if (_index < s_movementTests.size())
|
|
{
|
|
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
|
|
}
|
|
else
|
|
{
|
|
*ppDataRow = nullptr;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP Reset() override
|
|
{
|
|
_index = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
|
|
{
|
|
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
|
LONG index{ 0 };
|
|
auto dataNameBstr{ wil::make_bstr(L"index") };
|
|
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
|
|
*names = safeArray;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
|
|
{
|
|
*type = nullptr;
|
|
return S_OK;
|
|
}
|
|
|
|
private:
|
|
size_t _index{ 0 };
|
|
};
|
|
#pragma endregion
|
|
}
|
|
|
|
extern "C" HRESULT __declspec(dllexport) __cdecl GeneratedMovementTestDataSource(IDataSource** ppDataSource, void*)
|
|
{
|
|
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
|
|
return source.CopyTo(ppDataSource);
|
|
}
|
|
|
|
// UiaTextRange takes an object that implements
|
|
// IRawElementProviderSimple as a constructor argument. Making a real
|
|
// one would involve setting up the window which we don't want to do
|
|
// for unit tests so instead we'll use this one. We don't care about
|
|
// it not doing anything for its implementation because it is not used
|
|
// during the unit tests below.
|
|
class DummyElementProvider final : public ScreenInfoUiaProviderBase
|
|
{
|
|
public:
|
|
IFACEMETHODIMP Navigate(_In_ NavigateDirection /*direction*/,
|
|
_COM_Outptr_result_maybenull_ IRawElementProviderFragment** /*ppProvider*/) override
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
IFACEMETHODIMP get_BoundingRectangle(_Out_ UiaRect* /*pRect*/) override
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** /*ppProvider*/) override
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
void ChangeViewport(const SMALL_RECT /*NewWindow*/)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HRESULT GetSelectionRange(_In_ IRawElementProviderSimple* /*pProvider*/, const std::wstring_view /*wordDelimiters*/, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// degenerate range
|
|
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const /*pProvider*/, const std::wstring_view /*wordDelimiters*/, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// degenerate range at cursor position
|
|
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const /*pProvider*/,
|
|
const Cursor& /*cursor*/,
|
|
const std::wstring_view /*wordDelimiters*/,
|
|
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// specific endpoint range
|
|
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const /*pProvider*/,
|
|
const COORD /*start*/,
|
|
const COORD /*end*/,
|
|
const std::wstring_view /*wordDelimiters*/,
|
|
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// range from a UiaPoint
|
|
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const /*pProvider*/,
|
|
const UiaPoint /*point*/,
|
|
const std::wstring_view /*wordDelimiters*/,
|
|
_COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** /*ppUtr*/) override
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
};
|
|
|
|
class UiaTextRangeTests
|
|
{
|
|
TEST_CLASS(UiaTextRangeTests);
|
|
|
|
CommonState* _state;
|
|
DummyElementProvider _dummyProvider;
|
|
SCREEN_INFORMATION* _pScreenInfo;
|
|
TextBuffer* _pTextBuffer;
|
|
UiaTextRange* _range;
|
|
IUiaData* _pUiaData;
|
|
|
|
struct ExpectedResult
|
|
{
|
|
int moveAmt;
|
|
COORD start;
|
|
COORD end;
|
|
};
|
|
|
|
struct MoveTest
|
|
{
|
|
std::wstring comment;
|
|
COORD start;
|
|
COORD end;
|
|
int moveAmt;
|
|
ExpectedResult expected;
|
|
};
|
|
|
|
struct MoveEndpointTest
|
|
{
|
|
std::wstring comment;
|
|
COORD start;
|
|
COORD end;
|
|
int moveAmt;
|
|
TextPatternRangeEndpoint endpoint;
|
|
ExpectedResult expected;
|
|
};
|
|
|
|
struct ScrollTest
|
|
{
|
|
std::wstring comment;
|
|
short yPos;
|
|
};
|
|
|
|
static constexpr const wchar_t* toString(TextUnit unit) noexcept
|
|
{
|
|
// if a format is not supported, it goes to the next largest text unit
|
|
switch (unit)
|
|
{
|
|
case TextUnit_Character:
|
|
return L"Character";
|
|
case TextUnit_Format:
|
|
case TextUnit_Word:
|
|
return L"Word";
|
|
case TextUnit_Line:
|
|
return L"Line";
|
|
case TextUnit_Paragraph:
|
|
case TextUnit_Page:
|
|
case TextUnit_Document:
|
|
default:
|
|
return L"Document";
|
|
}
|
|
};
|
|
|
|
TEST_METHOD_SETUP(MethodSetup)
|
|
{
|
|
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
|
// set up common state
|
|
_state = new CommonState();
|
|
_state->PrepareGlobalFont();
|
|
_state->PrepareGlobalScreenBuffer();
|
|
_state->PrepareNewTextBufferInfo();
|
|
|
|
// set up pointers
|
|
_pScreenInfo = &gci.GetActiveOutputBuffer();
|
|
_pTextBuffer = &_pScreenInfo->GetTextBuffer();
|
|
_pUiaData = &gci.renderData;
|
|
|
|
// GH#6986: document end now limits the navigation to be
|
|
// within the document end bounds _as opposed to_ the buffer bounds.
|
|
// As a result, let's populate the buffer partially to define a document end.
|
|
// Additionally, add spaces to create "words" in the buffer.
|
|
|
|
// LOAD BEARING: make sure we fill it halfway so that we can reuse most of
|
|
// the variables from the generated tests.
|
|
|
|
// fill first half of text buffer with text
|
|
for (UINT i = 0; i < _pTextBuffer->TotalRowCount() / 2; ++i)
|
|
{
|
|
ROW& row = _pTextBuffer->GetRowByOffset(i);
|
|
auto& charRow = row.GetCharRow();
|
|
for (auto& cell : charRow)
|
|
{
|
|
if (i % 2 == 0)
|
|
{
|
|
cell.Char() = L' ';
|
|
}
|
|
else
|
|
{
|
|
cell.Char() = L'X';
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TEST_METHOD_CLEANUP(MethodCleanup)
|
|
{
|
|
_state->CleanupNewTextBufferInfo();
|
|
_state->CleanupGlobalScreenBuffer();
|
|
_state->CleanupGlobalFont();
|
|
delete _state;
|
|
delete _range;
|
|
|
|
_pScreenInfo = nullptr;
|
|
_pTextBuffer = nullptr;
|
|
_pUiaData = nullptr;
|
|
return true;
|
|
}
|
|
|
|
TEST_METHOD(DegenerateRangesDetected)
|
|
{
|
|
// make a degenerate range and verify that it reports degenerate
|
|
Microsoft::WRL::ComPtr<UiaTextRange> degenerate;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(°enerate,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
origin,
|
|
origin));
|
|
VERIFY_IS_TRUE(degenerate->IsDegenerate());
|
|
VERIFY_ARE_EQUAL(degenerate->_start, degenerate->_end);
|
|
|
|
// make a non-degenerate range and verify that it reports as such
|
|
const auto end{ point_offset_by_char(origin, bufferSize, 1) };
|
|
Microsoft::WRL::ComPtr<UiaTextRange> notDegenerate;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(¬Degenerate,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
origin,
|
|
end));
|
|
VERIFY_IS_FALSE(notDegenerate->IsDegenerate());
|
|
VERIFY_ARE_NOT_EQUAL(notDegenerate->_start, notDegenerate->_end);
|
|
}
|
|
|
|
TEST_METHOD(CompareRange)
|
|
{
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr1;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr1,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
origin,
|
|
origin));
|
|
|
|
// utr2 initialized to have the same start/end as utr1
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> utr2;
|
|
THROW_IF_FAILED(utr1->Clone(&utr2));
|
|
|
|
BOOL comparison;
|
|
Log::Comment(L"_start and _end should match");
|
|
THROW_IF_FAILED(utr1->Compare(utr2.Get(), &comparison));
|
|
VERIFY_IS_TRUE(comparison);
|
|
|
|
// utr2 redefined to have different end from utr1
|
|
const auto end{ point_offset_by_char(origin, bufferSize, 2) };
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr2,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
origin,
|
|
end));
|
|
|
|
Log::Comment(L"_end is different");
|
|
THROW_IF_FAILED(utr1->Compare(utr2.Get(), &comparison));
|
|
VERIFY_IS_FALSE(comparison);
|
|
}
|
|
|
|
TEST_METHOD(CompareEndpoints)
|
|
{
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr1;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr1,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
origin,
|
|
origin));
|
|
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> utr2;
|
|
THROW_IF_FAILED(utr1->Clone(&utr2));
|
|
|
|
int comparison;
|
|
Log::Comment(L"For a degenerate range, comparing _start and _end should return 0");
|
|
VERIFY_IS_TRUE(utr1->IsDegenerate());
|
|
THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_Start, utr1.Get(), TextPatternRangeEndpoint_End, &comparison));
|
|
|
|
Log::Comment(L"_start and _end should match");
|
|
THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_Start, utr2.Get(), TextPatternRangeEndpoint_Start, &comparison));
|
|
VERIFY_IS_TRUE(comparison == 0);
|
|
THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_End, utr2.Get(), TextPatternRangeEndpoint_End, &comparison));
|
|
VERIFY_IS_TRUE(comparison == 0);
|
|
|
|
// utr2 redefined to have different end from utr1
|
|
const auto end{ point_offset_by_char(origin, bufferSize, 2) };
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr2,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
origin,
|
|
end));
|
|
|
|
Log::Comment(L"_start should match");
|
|
THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_Start, utr2.Get(), TextPatternRangeEndpoint_Start, &comparison));
|
|
VERIFY_IS_TRUE(comparison == 0);
|
|
|
|
Log::Comment(L"_start and end should be 2 units apart. Sign depends on order of comparison.");
|
|
THROW_IF_FAILED(utr1->CompareEndpoints(TextPatternRangeEndpoint_End, utr2.Get(), TextPatternRangeEndpoint_End, &comparison));
|
|
VERIFY_IS_TRUE(comparison == -2);
|
|
THROW_IF_FAILED(utr2->CompareEndpoints(TextPatternRangeEndpoint_End, utr1.Get(), TextPatternRangeEndpoint_End, &comparison));
|
|
VERIFY_IS_TRUE(comparison == 2);
|
|
}
|
|
|
|
TEST_METHOD(ExpandToEnclosingUnit)
|
|
{
|
|
// Let's start by filling the text buffer with something useful:
|
|
for (UINT i = 0; i < _pTextBuffer->TotalRowCount(); ++i)
|
|
{
|
|
ROW& row = _pTextBuffer->GetRowByOffset(i);
|
|
auto& charRow = row.GetCharRow();
|
|
for (size_t j = 0; j < charRow.size(); ++j)
|
|
{
|
|
// every 5th cell is a space, otherwise a letter
|
|
// this is used to simulate words
|
|
CharRowCellReference cell = charRow.GlyphAt(j);
|
|
if (j % 5 == 0)
|
|
{
|
|
cell = L" ";
|
|
}
|
|
else
|
|
{
|
|
cell = L"x";
|
|
}
|
|
}
|
|
}
|
|
|
|
// According to https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-implementingtextandtextrange#manipulating-a-text-range-by-text-unit
|
|
// there are 9 examples of how ExpandToEnclosingUnit should behave. See the diagram there for reference.
|
|
// Some of the relevant text has been copied below...
|
|
// 1-2) If the text range starts at the beginning of a text unit
|
|
// and ends at the beginning of, or before, the next text unit
|
|
// boundary, the ending endpoint is moved to the next text unit boundary
|
|
// 3-4) If the text range starts at the beginning of a text unit
|
|
// and ends at, or after, the next unit boundary,
|
|
// the ending endpoint stays or is moved backward to
|
|
// the next unit boundary after the starting endpoint
|
|
// NOTE: If there is more than one text unit boundary between
|
|
// the starting and ending endpoints, the ending endpoint
|
|
// is moved backward to the next unit boundary after
|
|
// the starting endpoint, resulting in a text range that is
|
|
// one text unit in length.
|
|
// 5-8) If the text range starts in a middle of the text unit,
|
|
// the starting endpoint is moved backward to the beginning
|
|
// of the text unit, and the ending endpoint is moved forward
|
|
// or backward, as necessary, to the next unit boundary
|
|
// after the starting endpoint
|
|
// 9) (same as 1) If the text range starts and ends at the beginning of
|
|
// a text unit boundary, the ending endpoint is moved to the next text unit boundary
|
|
|
|
// We will abstract these tests so that we can define the beginning and end of a text unit boundary,
|
|
// based on the text unit we are testing
|
|
constexpr TextUnit supportedUnits[] = { TextUnit_Character, TextUnit_Word, TextUnit_Line, TextUnit_Document };
|
|
|
|
struct TextUnitBoundaries
|
|
{
|
|
COORD start;
|
|
COORD end;
|
|
};
|
|
|
|
const std::map<TextUnit, TextUnitBoundaries> textUnitBoundaries = {
|
|
{ TextUnit_Character,
|
|
TextUnitBoundaries{
|
|
{ 0, 0 },
|
|
{ 1, 0 } } },
|
|
{ TextUnit_Word,
|
|
TextUnitBoundaries{
|
|
{ 1, 0 },
|
|
{ 6, 0 } } },
|
|
{ TextUnit_Line,
|
|
TextUnitBoundaries{
|
|
{ 0, 0 },
|
|
{ 0, 1 } } },
|
|
{ TextUnit_Document,
|
|
TextUnitBoundaries{
|
|
{ 0, 0 },
|
|
_pTextBuffer->GetSize().EndExclusive() } }
|
|
};
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
auto verifyExpansion = [&](TextUnit textUnit, COORD utrStart, COORD utrEnd) {
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
utrStart,
|
|
utrEnd));
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(textUnit));
|
|
|
|
const auto boundaries = textUnitBoundaries.at(textUnit);
|
|
VERIFY_ARE_EQUAL(utr->GetEndpoint(TextPatternRangeEndpoint_Start), boundaries.start);
|
|
VERIFY_ARE_EQUAL(utr->GetEndpoint(TextPatternRangeEndpoint_End), boundaries.end);
|
|
};
|
|
|
|
for (auto textUnit : supportedUnits)
|
|
{
|
|
const auto boundaries = textUnitBoundaries.at(textUnit);
|
|
|
|
// Test 1
|
|
Log::Comment(NoThrowString().Format(L"%s - Test 1", toString(textUnit)));
|
|
verifyExpansion(textUnit, boundaries.start, boundaries.start);
|
|
|
|
// Test 2 (impossible for TextUnit_Character)
|
|
if (textUnit != TextUnit_Character)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"%s - Test 2", toString(textUnit)));
|
|
const COORD end = { boundaries.start.X + 1, boundaries.start.Y };
|
|
verifyExpansion(textUnit, boundaries.start, end);
|
|
}
|
|
|
|
// Test 3
|
|
Log::Comment(NoThrowString().Format(L"%s - Test 3", toString(textUnit)));
|
|
verifyExpansion(textUnit, boundaries.start, boundaries.end);
|
|
|
|
// Test 4 (impossible for TextUnit_Character and TextUnit_Document)
|
|
if (textUnit != TextUnit_Character && textUnit != TextUnit_Document)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"%s - Test 4", toString(textUnit)));
|
|
const COORD end = { boundaries.end.X + 1, boundaries.end.Y };
|
|
verifyExpansion(textUnit, boundaries.start, end);
|
|
}
|
|
|
|
// Test 5 (impossible for TextUnit_Character)
|
|
if (textUnit != TextUnit_Character)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"%s - Test 5", toString(textUnit)));
|
|
const COORD start = { boundaries.start.X + 1, boundaries.start.Y };
|
|
verifyExpansion(textUnit, start, start);
|
|
}
|
|
|
|
// Test 6 (impossible for TextUnit_Character)
|
|
if (textUnit != TextUnit_Character)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"%s - Test 6", toString(textUnit)));
|
|
const COORD start = { boundaries.start.X + 1, boundaries.start.Y };
|
|
const COORD end = { start.X + 1, start.Y };
|
|
verifyExpansion(textUnit, start, end);
|
|
}
|
|
|
|
// Test 7 (impossible for TextUnit_Character)
|
|
if (textUnit != TextUnit_Character)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"%s - Test 7", toString(textUnit)));
|
|
const COORD start = { boundaries.start.X + 1, boundaries.start.Y };
|
|
verifyExpansion(textUnit, start, boundaries.end);
|
|
}
|
|
|
|
// Test 8 (impossible for TextUnit_Character and TextUnit_Document)
|
|
if (textUnit != TextUnit_Character && textUnit != TextUnit_Document)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"%s - Test 8", toString(textUnit)));
|
|
const COORD start = { boundaries.start.X + 1, boundaries.start.Y };
|
|
const COORD end = { boundaries.end.X + 1, boundaries.end.Y };
|
|
verifyExpansion(textUnit, start, end);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(MoveEndpointByRange)
|
|
{
|
|
const COORD start{ 0, 1 };
|
|
const COORD end{ 1, 2 };
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
start,
|
|
end));
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> target;
|
|
|
|
auto resetTargetUTR = [&]() {
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&target,
|
|
_pUiaData,
|
|
&_dummyProvider,
|
|
origin,
|
|
origin));
|
|
};
|
|
|
|
Log::Comment(L"Move target's end to utr1's start");
|
|
{
|
|
resetTargetUTR();
|
|
THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_End,
|
|
utr.Get(),
|
|
TextPatternRangeEndpoint_Start));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), origin);
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_Start));
|
|
}
|
|
|
|
Log::Comment(L"Move target's start/end to utr1's start/end respectively");
|
|
{
|
|
resetTargetUTR();
|
|
THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_End,
|
|
utr.Get(),
|
|
TextPatternRangeEndpoint_End));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), origin);
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_End));
|
|
|
|
THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_Start,
|
|
utr.Get(),
|
|
TextPatternRangeEndpoint_Start));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_Start));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_End));
|
|
}
|
|
|
|
Log::Comment(L"(Clone utr1) Collapse onto itself");
|
|
{
|
|
// Move start to end
|
|
ComPtr<ITextRangeProvider> temp;
|
|
THROW_IF_FAILED(utr->Clone(&temp));
|
|
target = static_cast<UiaTextRange*>(temp.Get());
|
|
THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_Start,
|
|
target.Get(),
|
|
TextPatternRangeEndpoint_End));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_End));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_End));
|
|
|
|
// Move end to start
|
|
THROW_IF_FAILED(utr->Clone(&temp));
|
|
target = static_cast<UiaTextRange*>(temp.Get());
|
|
THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_End,
|
|
target.Get(),
|
|
TextPatternRangeEndpoint_Start));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_Start));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_Start));
|
|
}
|
|
|
|
Log::Comment(L"Cross endpoints (force degenerate range)");
|
|
{
|
|
// move start past end
|
|
resetTargetUTR();
|
|
THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_Start,
|
|
utr.Get(),
|
|
TextPatternRangeEndpoint_End));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_End));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_End));
|
|
VERIFY_IS_TRUE(target->IsDegenerate());
|
|
|
|
// move end past start
|
|
THROW_IF_FAILED(target->MoveEndpointByRange(TextPatternRangeEndpoint_End,
|
|
utr.Get(),
|
|
TextPatternRangeEndpoint_Start));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_Start), utr->GetEndpoint(TextPatternRangeEndpoint_Start));
|
|
VERIFY_ARE_EQUAL(target->GetEndpoint(TextPatternRangeEndpoint_End), utr->GetEndpoint(TextPatternRangeEndpoint_Start));
|
|
VERIFY_IS_TRUE(target->IsDegenerate());
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(CanMoveByCharacter)
|
|
{
|
|
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().RightInclusive();
|
|
|
|
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
|
// instead of parsing through thousands of empty lines of text.
|
|
const COORD documentEnd{ _pTextBuffer->GetSize().Left(), _pTextBuffer->GetLastNonSpaceCharacter().Y + 1 };
|
|
|
|
// clang-format off
|
|
const std::vector<MoveTest> testData
|
|
{
|
|
MoveTest{
|
|
L"can move backward within a row",
|
|
{ 1, 0 },
|
|
{ 2, 0 },
|
|
-1,
|
|
{
|
|
-1,
|
|
{0, 0},
|
|
{1, 0}
|
|
}
|
|
},
|
|
|
|
MoveTest{
|
|
L"can move forward in a row",
|
|
{ 1, 2 },
|
|
{ 5, 4 },
|
|
5,
|
|
{
|
|
5,
|
|
{6,2},
|
|
{7,2}
|
|
}
|
|
},
|
|
|
|
MoveTest{
|
|
L"can't move past the end of the 'document'",
|
|
documentEnd,
|
|
documentEnd,
|
|
5,
|
|
{
|
|
0,
|
|
documentEnd,
|
|
documentEnd,
|
|
}
|
|
},
|
|
|
|
MoveTest{
|
|
L"can move to a new row when necessary when moving forward",
|
|
{ lastColumnIndex, 0 },
|
|
{ lastColumnIndex, 0 },
|
|
5,
|
|
{
|
|
5,
|
|
{4 , 0 + 1},
|
|
{4 , 0 + 1}
|
|
}
|
|
},
|
|
|
|
MoveTest{
|
|
L"can move to a new row when necessary when moving backward",
|
|
{ 0, 0 + 1 },
|
|
{ lastColumnIndex, 0 + 1 },
|
|
-5,
|
|
{
|
|
-5,
|
|
{lastColumnIndex - 4, 0},
|
|
{lastColumnIndex - 3, 0}
|
|
}
|
|
}
|
|
};
|
|
// clang-format on
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
for (const auto& test : testData)
|
|
{
|
|
Log::Comment(test.comment.data());
|
|
int amountMoved;
|
|
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, test.start, test.end));
|
|
THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Character, test.moveAmt, &amountMoved));
|
|
|
|
VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved);
|
|
VERIFY_ARE_EQUAL(test.expected.start, utr->_start);
|
|
VERIFY_ARE_EQUAL(test.expected.end, utr->_end);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(CanMoveByLine)
|
|
{
|
|
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
|
|
|
|
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
|
// instead of parsing through thousands of empty lines of text.
|
|
const COORD documentEnd{ _pTextBuffer->GetSize().Left(), _pTextBuffer->GetLastNonSpaceCharacter().Y + 1 };
|
|
|
|
// clang-format off
|
|
const std::vector<MoveTest> testData
|
|
{
|
|
MoveTest{
|
|
L"can move forward from top row",
|
|
{0, 0},
|
|
{0, lastColumnIndex},
|
|
4,
|
|
{
|
|
4,
|
|
{0, 4},
|
|
{0, 5}
|
|
}
|
|
},
|
|
|
|
MoveTest{
|
|
L"can't move past the end of the 'document'",
|
|
documentEnd,
|
|
documentEnd,
|
|
5,
|
|
{
|
|
0,
|
|
documentEnd,
|
|
documentEnd,
|
|
}
|
|
},
|
|
|
|
MoveTest{
|
|
L"can move backward from bottom row",
|
|
{0, documentEnd.Y},
|
|
{lastColumnIndex, documentEnd.Y},
|
|
-3,
|
|
{
|
|
-3,
|
|
{0, base::ClampSub(documentEnd.Y, 3)},
|
|
{0, base::ClampSub(documentEnd.Y, 3)}
|
|
}
|
|
},
|
|
|
|
MoveTest{
|
|
L"can move to a new row when necessary when moving forward",
|
|
{ lastColumnIndex, 0 },
|
|
{ lastColumnIndex, 0 },
|
|
5,
|
|
{
|
|
5,
|
|
{0, 0 + 5},
|
|
{0, 0 + 5}
|
|
}
|
|
},
|
|
|
|
MoveTest{
|
|
L"can move to a new row when necessary when moving backward",
|
|
{ 0, 7 },
|
|
{ 0, 7 },
|
|
-5,
|
|
{
|
|
-5,
|
|
{0, 7 - 5},
|
|
{0, 7 - 5}
|
|
}
|
|
}
|
|
};
|
|
// clang-format on
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
for (const auto& test : testData)
|
|
{
|
|
Log::Comment(test.comment.data());
|
|
int amountMoved;
|
|
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, test.start, test.end));
|
|
THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Line, test.moveAmt, &amountMoved));
|
|
|
|
VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved);
|
|
VERIFY_ARE_EQUAL(test.expected.start, utr->_start);
|
|
VERIFY_ARE_EQUAL(test.expected.end, utr->_end);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(CanMoveEndpointByUnitCharacter)
|
|
{
|
|
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
|
|
|
|
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
|
// instead of parsing through thousands of empty lines of text.
|
|
const COORD documentEnd{ _pTextBuffer->GetSize().RightInclusive(), _pTextBuffer->GetLastNonSpaceCharacter().Y };
|
|
|
|
// clang-format off
|
|
const std::vector<MoveEndpointTest> testData
|
|
{
|
|
MoveEndpointTest{
|
|
L"can't move _start past the beginning of the document when _start is positioned at the beginning",
|
|
{0, 0},
|
|
{lastColumnIndex, 0},
|
|
-1,
|
|
TextPatternRangeEndpoint_Start,
|
|
{
|
|
0,
|
|
{0, 0},
|
|
{lastColumnIndex, 0}
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can partially move _start to the beginning of the document when it is closer than the move count requested",
|
|
{3, 0},
|
|
{lastColumnIndex, 0},
|
|
-5,
|
|
TextPatternRangeEndpoint_Start,
|
|
{
|
|
-3,
|
|
{0, 0},
|
|
{lastColumnIndex, 0}
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _end past the beginning of the document",
|
|
{0, 0},
|
|
{4, 0},
|
|
-5,
|
|
TextPatternRangeEndpoint_End,
|
|
{
|
|
-4,
|
|
{0, 0},
|
|
{0, 0}
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _end past the end of the document",
|
|
{0, 0},
|
|
documentEnd,
|
|
5,
|
|
TextPatternRangeEndpoint_End,
|
|
{
|
|
1,
|
|
{0,0},
|
|
{0, base::ClampAdd(documentEnd.Y,1)}
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"_start follows _end when passed during movement",
|
|
{5, 0},
|
|
{10, 0},
|
|
-7,
|
|
TextPatternRangeEndpoint_End,
|
|
{
|
|
-7,
|
|
{3, 0},
|
|
{3, 0}
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _end past the beginning of the document when _end is positioned at the end",
|
|
{0, documentEnd.Y},
|
|
{0, base::ClampAdd(documentEnd.Y,1)},
|
|
1,
|
|
TextPatternRangeEndpoint_End,
|
|
{
|
|
0,
|
|
{0, documentEnd.Y},
|
|
{0, base::ClampAdd(documentEnd.Y,1)},
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can partially move _end to the end of the document when it is closer than the move count requested",
|
|
{0, 0},
|
|
{base::ClampSub(lastColumnIndex, 3), documentEnd.Y},
|
|
5,
|
|
TextPatternRangeEndpoint_End,
|
|
{
|
|
4,
|
|
{0, 0},
|
|
{0, base::ClampAdd(documentEnd.Y,1)},
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _start past the end of the document",
|
|
{base::ClampSub(lastColumnIndex, 4), documentEnd.Y},
|
|
{0, base::ClampAdd(documentEnd.Y,1)},
|
|
5,
|
|
TextPatternRangeEndpoint_Start,
|
|
{
|
|
5,
|
|
{0, base::ClampAdd(documentEnd.Y,1)},
|
|
{0, base::ClampAdd(documentEnd.Y,1)},
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"_end follows _start when passed during movement",
|
|
{5, 0},
|
|
{10, 0},
|
|
7,
|
|
TextPatternRangeEndpoint_Start,
|
|
{
|
|
7,
|
|
{12, 0},
|
|
{12, 0}
|
|
}
|
|
},
|
|
};
|
|
// clang-format on
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
for (const auto& test : testData)
|
|
{
|
|
Log::Comment(test.comment.data());
|
|
int amountMoved;
|
|
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, test.start, test.end));
|
|
THROW_IF_FAILED(utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Character, test.moveAmt, &amountMoved));
|
|
|
|
VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved);
|
|
VERIFY_ARE_EQUAL(test.expected.start, utr->_start);
|
|
VERIFY_ARE_EQUAL(test.expected.end, utr->_end);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(CanMoveEndpointByUnitLine)
|
|
{
|
|
const SHORT lastColumnIndex = _pScreenInfo->GetBufferSize().Width() - 1;
|
|
const SHORT bottomRow = gsl::narrow<SHORT>(_pTextBuffer->TotalRowCount() - 1);
|
|
|
|
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
|
// instead of parsing through thousands of empty lines of text.
|
|
const COORD documentEnd{ _pTextBuffer->GetSize().Left(), _pTextBuffer->GetLastNonSpaceCharacter().Y + 1 };
|
|
|
|
// clang-format off
|
|
const std::vector<MoveEndpointTest> testData
|
|
{
|
|
MoveEndpointTest{
|
|
L"can move _end forward without affecting _start",
|
|
{0, 0},
|
|
{lastColumnIndex, 0},
|
|
1,
|
|
TextPatternRangeEndpoint_End,
|
|
1,
|
|
{0, 0},
|
|
{0, 1}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can move _end backward without affecting _start",
|
|
{0, 1},
|
|
{lastColumnIndex, 5},
|
|
-2,
|
|
TextPatternRangeEndpoint_End,
|
|
-2,
|
|
{0, 1},
|
|
{0, 4}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can move _start forward without affecting _end",
|
|
{0, 1},
|
|
{lastColumnIndex, 5},
|
|
2,
|
|
TextPatternRangeEndpoint_Start,
|
|
2,
|
|
{0, 3},
|
|
{lastColumnIndex, 5}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can move _start backward without affecting _end",
|
|
{0, 2},
|
|
{lastColumnIndex, 5},
|
|
-1,
|
|
TextPatternRangeEndpoint_Start,
|
|
-1,
|
|
{0, 1},
|
|
{lastColumnIndex, 5}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can move _start backwards when it's already on the top row",
|
|
{lastColumnIndex, 0},
|
|
{lastColumnIndex, 0},
|
|
-1,
|
|
TextPatternRangeEndpoint_Start,
|
|
-1,
|
|
{0, 0},
|
|
{lastColumnIndex, 0},
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _start backwards when it's at the start of the document already",
|
|
{0, 0},
|
|
{lastColumnIndex, 0},
|
|
-1,
|
|
TextPatternRangeEndpoint_Start,
|
|
0,
|
|
{0, 0},
|
|
{lastColumnIndex, 0}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _end forwards when it's on the bottom row (past doc end)",
|
|
{0, 0},
|
|
{lastColumnIndex - 3, bottomRow},
|
|
1,
|
|
TextPatternRangeEndpoint_End,
|
|
0,
|
|
{0, 0},
|
|
documentEnd
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _end forwards when it's at the end of the buffer already (past doc end)",
|
|
{0, 0},
|
|
{0, bottomRow+1},
|
|
1,
|
|
TextPatternRangeEndpoint_End,
|
|
0,
|
|
{0, 0},
|
|
documentEnd
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"moving _start forward when it's already on the bottom row (past doc end) creates a degenerate range at the document end",
|
|
{0, bottomRow},
|
|
{lastColumnIndex, bottomRow},
|
|
1,
|
|
TextPatternRangeEndpoint_Start,
|
|
0,
|
|
documentEnd,
|
|
documentEnd
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"moving _end backward when it's already on the top row creates a degenerate range at the document start",
|
|
{4, 0},
|
|
{lastColumnIndex - 5, 0},
|
|
-1,
|
|
TextPatternRangeEndpoint_End,
|
|
-1,
|
|
{0, 0},
|
|
{0, 0}
|
|
}
|
|
};
|
|
// clang-format on
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
for (const auto& test : testData)
|
|
{
|
|
Log::Comment(test.comment.data());
|
|
int amountMoved;
|
|
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, test.start, test.end));
|
|
THROW_IF_FAILED(utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Line, test.moveAmt, &amountMoved));
|
|
|
|
VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved);
|
|
VERIFY_ARE_EQUAL(test.expected.start, utr->_start);
|
|
VERIFY_ARE_EQUAL(test.expected.end, utr->_end);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(CanMoveEndpointByUnitDocument)
|
|
{
|
|
const SHORT bottomRow = gsl::narrow<SHORT>(_pTextBuffer->TotalRowCount() - 1);
|
|
|
|
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
|
// instead of parsing through thousands of empty lines of text.
|
|
const COORD documentEnd{ _pTextBuffer->GetSize().Left(), _pTextBuffer->GetLastNonSpaceCharacter().Y + 1 };
|
|
|
|
// clang-format off
|
|
const std::vector<MoveEndpointTest> testData =
|
|
{
|
|
MoveEndpointTest{
|
|
L"can move _end forward to end of document without affecting _start",
|
|
{0, 4},
|
|
{0, 4},
|
|
1,
|
|
TextPatternRangeEndpoint_End,
|
|
{
|
|
1,
|
|
{0, 4},
|
|
documentEnd
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can move _start backward to end of document without affect _end",
|
|
{0, 4},
|
|
{0, 4},
|
|
-1,
|
|
TextPatternRangeEndpoint_Start,
|
|
{
|
|
-1,
|
|
{0, 0},
|
|
{0, 4}
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _end forward when it's already at the end of the buffer (past doc end)",
|
|
{3, 2},
|
|
{0, bottomRow+1},
|
|
1,
|
|
TextPatternRangeEndpoint_End,
|
|
{
|
|
0,
|
|
{3, 2},
|
|
documentEnd
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"can't move _start backward when it's already at the start of the document",
|
|
{0, 0},
|
|
{5, 6},
|
|
-1,
|
|
TextPatternRangeEndpoint_Start,
|
|
{
|
|
0,
|
|
{0, 0},
|
|
{5, 6}
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"moving _end backward creates degenerate range at start of document",
|
|
{5, 2},
|
|
{5, 6},
|
|
-1,
|
|
TextPatternRangeEndpoint_End,
|
|
{
|
|
-1,
|
|
{0, 0},
|
|
{0, 0}
|
|
}
|
|
},
|
|
|
|
MoveEndpointTest{
|
|
L"moving _start forward creates degenerate range at end of document",
|
|
{5, 2},
|
|
{5, 6},
|
|
1,
|
|
TextPatternRangeEndpoint_Start,
|
|
{
|
|
1,
|
|
documentEnd,
|
|
documentEnd
|
|
}
|
|
}
|
|
};
|
|
// clang-format on
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
for (auto test : testData)
|
|
{
|
|
Log::Comment(test.comment.c_str());
|
|
int amountMoved;
|
|
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, test.start, test.end));
|
|
THROW_IF_FAILED(utr->MoveEndpointByUnit(test.endpoint, TextUnit::TextUnit_Document, test.moveAmt, &amountMoved));
|
|
|
|
VERIFY_ARE_EQUAL(test.expected.moveAmt, amountMoved);
|
|
VERIFY_ARE_EQUAL(test.expected.start, utr->_start);
|
|
VERIFY_ARE_EQUAL(test.expected.end, utr->_end);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(ExpansionAtExclusiveEnd)
|
|
{
|
|
// GH#7664: When attempting to expand to an enclosing unit
|
|
// at the end exclusive, the UTR should refuse to move past
|
|
// the end.
|
|
const auto lastNonspaceCharPos{ _pTextBuffer->GetLastNonSpaceCharacter() };
|
|
const COORD documentEnd{ 0, lastNonspaceCharPos.Y + 1 };
|
|
|
|
// Iterate over each TextUnit. If we don't support
|
|
// the given TextUnit, we're supposed to fallback
|
|
// to the last one that was defined anyways.
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:textUnit", L"{0, 1, 2, 3, 4, 5, 6}")
|
|
END_TEST_METHOD_PROPERTIES();
|
|
|
|
int textUnit;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"textUnit", textUnit), L"Get textUnit variant");
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
Log::Comment(NoThrowString().Format(L"%s", toString(static_cast<TextUnit>(textUnit))));
|
|
|
|
// Create a degenerate UTR at EndExclusive
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, bufferEnd, endExclusive));
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(static_cast<TextUnit>(textUnit)));
|
|
|
|
VERIFY_ARE_EQUAL(documentEnd, til::point{ utr->_end });
|
|
}
|
|
|
|
TEST_METHOD(MovementAtExclusiveEnd)
|
|
{
|
|
// GH#7663: When attempting to move from end exclusive,
|
|
// the UTR should refuse to move past the end.
|
|
const auto endInclusive{ bufferEnd };
|
|
|
|
// write "temp" at (2,2)
|
|
_pTextBuffer->Reset();
|
|
const til::point writeTarget{ 2, 2 };
|
|
_pTextBuffer->Write({ L"temp" }, writeTarget);
|
|
|
|
// GH#6986: This is used as the "end of the buffer" to help screen readers run faster
|
|
// instead of parsing through thousands of empty lines of text.
|
|
const COORD documentEndInclusive{ base::ClampSub<short, short>(static_cast<short>(bufferSize.right()), 1), _pTextBuffer->GetLastNonSpaceCharacter().Y };
|
|
const COORD documentEndExclusive{ static_cast<short>(bufferSize.left()), base::ClampAdd(documentEndInclusive.Y, 1) };
|
|
|
|
const COORD lastLineStart{ static_cast<short>(bufferSize.left()), documentEndInclusive.Y };
|
|
const auto secondToLastLinePos{ point_offset_by_line(lastLineStart, bufferSize, -1) };
|
|
const COORD secondToLastCharacterPos{ documentEndInclusive.X - 1, documentEndInclusive.Y };
|
|
|
|
// Iterate over each TextUnit. If we don't support
|
|
// the given TextUnit, we're supposed to fallback
|
|
// to the last one that was defined anyways.
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:textUnit", L"{0, 1, 2, 3, 4, 5, 6}")
|
|
TEST_METHOD_PROPERTY(L"Data:degenerate", L"{false, true}")
|
|
TEST_METHOD_PROPERTY(L"Data:atDocumentEnd", L"{false, true}")
|
|
END_TEST_METHOD_PROPERTIES();
|
|
|
|
int unit;
|
|
bool degenerate;
|
|
bool atDocumentEnd;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"textUnit", unit), L"Get TextUnit variant");
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"degenerate", degenerate), L"Get degenerate variant");
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"atDocumentEnd", atDocumentEnd), L"Get atDocumentEnd variant");
|
|
TextUnit textUnit{ static_cast<TextUnit>(unit) };
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
int moveAmt;
|
|
Log::Comment(NoThrowString().Format(L"Forward by %s", toString(textUnit)));
|
|
|
|
// Create an UTR at EndExclusive
|
|
const auto utrEnd{ atDocumentEnd ? documentEndExclusive : static_cast<COORD>(endExclusive) };
|
|
if (degenerate)
|
|
{
|
|
// UTR: (exclusive, exclusive) range
|
|
const auto utrStart{ atDocumentEnd ? documentEndExclusive : static_cast<COORD>(endExclusive) };
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, utrStart, utrEnd));
|
|
}
|
|
else
|
|
{
|
|
// UTR: (inclusive, exclusive) range
|
|
const auto utrStart{ atDocumentEnd ? documentEndInclusive : static_cast<COORD>(endInclusive) };
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, utrStart, utrEnd));
|
|
}
|
|
THROW_IF_FAILED(utr->Move(textUnit, 1, &moveAmt));
|
|
|
|
VERIFY_ARE_EQUAL(documentEndExclusive, utr->_end);
|
|
VERIFY_ARE_EQUAL(0, moveAmt);
|
|
|
|
// Verify expansion works properly
|
|
Log::Comment(NoThrowString().Format(L"Expand by %s", toString(textUnit)));
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(textUnit));
|
|
if (textUnit <= TextUnit::TextUnit_Character)
|
|
{
|
|
VERIFY_ARE_EQUAL(documentEndInclusive, utr->_start);
|
|
VERIFY_ARE_EQUAL(documentEndExclusive, utr->_end);
|
|
}
|
|
else if (textUnit <= TextUnit::TextUnit_Word)
|
|
{
|
|
VERIFY_ARE_EQUAL(writeTarget, til::point{ utr->_start });
|
|
VERIFY_ARE_EQUAL(documentEndExclusive, utr->_end);
|
|
}
|
|
else if (textUnit <= TextUnit::TextUnit_Line)
|
|
{
|
|
VERIFY_ARE_EQUAL(lastLineStart, utr->_start);
|
|
VERIFY_ARE_EQUAL(documentEndExclusive, utr->_end);
|
|
}
|
|
else // textUnit <= TextUnit::TextUnit_Document:
|
|
{
|
|
VERIFY_ARE_EQUAL(origin, til::point{ utr->_start });
|
|
VERIFY_ARE_EQUAL(documentEndExclusive, utr->_end);
|
|
}
|
|
|
|
// reset the UTR
|
|
if (degenerate)
|
|
{
|
|
// UTR: (exclusive, exclusive) range
|
|
const auto utrStart{ atDocumentEnd ? documentEndExclusive : static_cast<COORD>(endExclusive) };
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, utrStart, utrEnd));
|
|
}
|
|
else
|
|
{
|
|
// UTR: (inclusive, exclusive) range
|
|
const auto utrStart{ atDocumentEnd ? documentEndInclusive : static_cast<COORD>(endInclusive) };
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, utrStart, utrEnd));
|
|
}
|
|
|
|
// Verify that moving backwards still works properly
|
|
Log::Comment(NoThrowString().Format(L"Backwards by %s", toString(textUnit)));
|
|
THROW_IF_FAILED(utr->Move(textUnit, -1, &moveAmt));
|
|
|
|
// NOTE: If the range is degenerate, _start == _end before AND after the move.
|
|
if (textUnit <= TextUnit::TextUnit_Character)
|
|
{
|
|
// Special case: _end will always be endInclusive, because...
|
|
// - degenerate --> it moves with _start to stay degenerate
|
|
// - !degenerate --> it excludes the last char, to select the second to last char
|
|
VERIFY_ARE_EQUAL(-1, moveAmt);
|
|
VERIFY_ARE_EQUAL(degenerate || !atDocumentEnd ? documentEndInclusive : secondToLastCharacterPos, utr->_start);
|
|
VERIFY_ARE_EQUAL(documentEndInclusive, utr->_end);
|
|
}
|
|
else if (textUnit <= TextUnit::TextUnit_Word)
|
|
{
|
|
VERIFY_ARE_EQUAL(-1, moveAmt);
|
|
VERIFY_ARE_EQUAL(degenerate || !atDocumentEnd ? writeTarget : origin, til::point{ utr->_start });
|
|
VERIFY_ARE_EQUAL(writeTarget, til::point{ utr->_end });
|
|
}
|
|
else if (textUnit <= TextUnit::TextUnit_Line)
|
|
{
|
|
VERIFY_ARE_EQUAL(-1, moveAmt);
|
|
VERIFY_ARE_EQUAL(degenerate || !atDocumentEnd ? lastLineStart : static_cast<COORD>(secondToLastLinePos), utr->_start);
|
|
VERIFY_ARE_EQUAL(lastLineStart, utr->_end);
|
|
}
|
|
else // textUnit <= TextUnit::TextUnit_Document:
|
|
{
|
|
VERIFY_ARE_EQUAL(degenerate || !atDocumentEnd ? -1 : 0, moveAmt);
|
|
VERIFY_ARE_EQUAL(origin, til::point{ utr->_start });
|
|
VERIFY_ARE_EQUAL(degenerate || !atDocumentEnd ? static_cast<COORD>(origin) : documentEndExclusive, utr->_end);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(MoveToPreviousWord)
|
|
{
|
|
// See GH#7742 for more details.
|
|
|
|
const auto originExclusive{ point_offset_by_char(origin, bufferSize, 1) };
|
|
|
|
_pTextBuffer->Write({ L"My name is Carlos" }, origin);
|
|
|
|
// Create degenerate UTR at origin
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, origin, origin));
|
|
|
|
// move forward by a word
|
|
int moveAmt;
|
|
THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Word, 1, &moveAmt));
|
|
VERIFY_ARE_EQUAL(1, moveAmt);
|
|
VERIFY_IS_TRUE(utr->IsDegenerate());
|
|
|
|
// Expand by word
|
|
BSTR text;
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit::TextUnit_Word));
|
|
THROW_IF_FAILED(utr->GetText(-1, &text));
|
|
VERIFY_ARE_EQUAL(L"name ", std::wstring_view{ text });
|
|
|
|
// Collapse utr (move end to start)
|
|
const COORD expectedStart{ 3, 0 };
|
|
THROW_IF_FAILED(utr->MoveEndpointByRange(TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, utr.Get(), TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start));
|
|
VERIFY_ARE_EQUAL(expectedStart, utr->_start);
|
|
VERIFY_IS_TRUE(utr->IsDegenerate());
|
|
|
|
// Move back by a word
|
|
THROW_IF_FAILED(utr->Move(TextUnit::TextUnit_Word, -1, &moveAmt));
|
|
VERIFY_ARE_EQUAL(-1, moveAmt);
|
|
|
|
// Expand by character
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit::TextUnit_Character));
|
|
THROW_IF_FAILED(utr->GetText(-1, &text));
|
|
VERIFY_ARE_EQUAL(L"M", std::wstring_view{ text });
|
|
}
|
|
|
|
TEST_METHOD(ScrollIntoView)
|
|
{
|
|
const auto viewportSize{ _pUiaData->GetViewport() };
|
|
|
|
const std::vector<ScrollTest> testData{
|
|
{ L"Origin", gsl::narrow<short>(bufferSize.top()) },
|
|
{ L"ViewportHeight From Top - 1", base::ClampedNumeric<short>(bufferSize.top()) + viewportSize.Height() - 1 },
|
|
{ L"ViewportHeight From Top", base::ClampedNumeric<short>(bufferSize.top()) + viewportSize.Height() },
|
|
{ L"ViewportHeight From Top + 1", base::ClampedNumeric<short>(bufferSize.top()) + viewportSize.Height() + 1 },
|
|
{ L"ViewportHeight From Bottom - 1", base::ClampedNumeric<short>(bufferSize.bottom()) - viewportSize.Height() - 2 },
|
|
{ L"ViewportHeight From Bottom", base::ClampedNumeric<short>(bufferSize.bottom()) - viewportSize.Height() - 1 },
|
|
{ L"ViewportHeight From Bottom + 1", base::ClampedNumeric<short>(bufferSize.bottom()) - viewportSize.Height() + 1 },
|
|
|
|
// GH#7839: ExclusiveEnd is a non-existent space,
|
|
// so scrolling to it when !alignToTop used to crash
|
|
{ L"Exclusive End", gsl::narrow<short>(bufferSize.bottom()) }
|
|
};
|
|
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:alignToTop", L"{false, true}")
|
|
END_TEST_METHOD_PROPERTIES();
|
|
|
|
bool alignToTop;
|
|
VERIFY_SUCCEEDED(TestData::TryGetValue(L"alignToTop", alignToTop), L"Get alignToTop variant");
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
for (const auto test : testData)
|
|
{
|
|
Log::Comment(test.comment.c_str());
|
|
const til::point pos{ bufferSize.left(), test.yPos };
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, pos, pos));
|
|
VERIFY_SUCCEEDED(utr->ScrollIntoView(alignToTop));
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(GetAttributeValue)
|
|
{
|
|
Log::Comment(L"Check supported attributes");
|
|
Microsoft::WRL::ComPtr<IUnknown> notSupportedVal;
|
|
UiaGetReservedNotSupportedValue(¬SupportedVal);
|
|
|
|
// Iterate over UIA's Text Attribute Identifiers
|
|
// Validate that we know which ones are (not) supported
|
|
// source: https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids
|
|
for (long uiaAttributeId = UIA_AnimationStyleAttributeId; uiaAttributeId <= UIA_AfterParagraphSpacingAttributeId; ++uiaAttributeId)
|
|
{
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider));
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Character));
|
|
|
|
Log::Comment(NoThrowString().Format(L"Attribute ID: %d", uiaAttributeId));
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(uiaAttributeId, &result));
|
|
|
|
switch (uiaAttributeId)
|
|
{
|
|
case UIA_FontNameAttributeId:
|
|
{
|
|
VERIFY_ARE_EQUAL(VT_BSTR, result.vt);
|
|
break;
|
|
}
|
|
case UIA_BackgroundColorAttributeId:
|
|
case UIA_FontWeightAttributeId:
|
|
case UIA_ForegroundColorAttributeId:
|
|
case UIA_StrikethroughStyleAttributeId:
|
|
case UIA_UnderlineStyleAttributeId:
|
|
{
|
|
VERIFY_ARE_EQUAL(VT_I4, result.vt);
|
|
break;
|
|
}
|
|
case UIA_IsItalicAttributeId:
|
|
case UIA_IsReadOnlyAttributeId:
|
|
{
|
|
VERIFY_ARE_EQUAL(VT_BOOL, result.vt);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
// Expected: not supported
|
|
VERIFY_ARE_EQUAL(VT_UNKNOWN, result.vt);
|
|
VERIFY_ARE_EQUAL(notSupportedVal.Get(), result.punkVal);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is the text attribute we'll use to update the text buffer.
|
|
// We'll modify it, then test if the UiaTextRange can extract/interpret the data properly.
|
|
// updateBuffer() will write that text attribute to the first cell in the buffer.
|
|
TextAttribute attr;
|
|
auto updateBuffer = [&](TextAttribute outputAttr) {
|
|
_pTextBuffer->Write({ outputAttr }, { 0, 0 });
|
|
};
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider));
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Character));
|
|
{
|
|
Log::Comment(L"Test Background");
|
|
const auto rawBackgroundColor{ RGB(255, 0, 0) };
|
|
attr.SetBackground(rawBackgroundColor);
|
|
updateBuffer(attr);
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_BackgroundColorAttributeId, &result));
|
|
|
|
const COLORREF realBackgroundColor{ _pUiaData->GetAttributeColors(attr).second & 0x00ffffff };
|
|
VERIFY_ARE_EQUAL(realBackgroundColor, static_cast<COLORREF>(result.lVal));
|
|
}
|
|
{
|
|
Log::Comment(L"Test Font Weight");
|
|
attr.SetBold(true);
|
|
updateBuffer(attr);
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontWeightAttributeId, &result));
|
|
VERIFY_ARE_EQUAL(FW_BOLD, result.lVal);
|
|
|
|
attr.SetBold(false);
|
|
updateBuffer(attr);
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontWeightAttributeId, &result));
|
|
VERIFY_ARE_EQUAL(FW_NORMAL, result.lVal);
|
|
;
|
|
}
|
|
{
|
|
Log::Comment(L"Test Foreground");
|
|
const auto rawForegroundColor{ RGB(255, 0, 0) };
|
|
attr.SetForeground(rawForegroundColor);
|
|
updateBuffer(attr);
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_ForegroundColorAttributeId, &result));
|
|
|
|
const auto realForegroundColor{ _pUiaData->GetAttributeColors(attr).first & 0x00ffffff };
|
|
VERIFY_ARE_EQUAL(realForegroundColor, static_cast<COLORREF>(result.lVal));
|
|
}
|
|
{
|
|
Log::Comment(L"Test Italic");
|
|
attr.SetItalic(true);
|
|
updateBuffer(attr);
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsItalicAttributeId, &result));
|
|
VERIFY_IS_TRUE(result.boolVal);
|
|
|
|
attr.SetItalic(false);
|
|
updateBuffer(attr);
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsItalicAttributeId, &result));
|
|
VERIFY_IS_FALSE(result.boolVal);
|
|
}
|
|
{
|
|
Log::Comment(L"Test Strikethrough");
|
|
attr.SetCrossedOut(true);
|
|
updateBuffer(attr);
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_StrikethroughStyleAttributeId, &result));
|
|
VERIFY_ARE_EQUAL(TextDecorationLineStyle_Single, result.lVal);
|
|
|
|
attr.SetCrossedOut(false);
|
|
updateBuffer(attr);
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_StrikethroughStyleAttributeId, &result));
|
|
VERIFY_ARE_EQUAL(TextDecorationLineStyle_None, result.lVal);
|
|
}
|
|
{
|
|
Log::Comment(L"Test Underline");
|
|
|
|
// Single underline
|
|
attr.SetUnderlined(true);
|
|
updateBuffer(attr);
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result));
|
|
VERIFY_ARE_EQUAL(TextDecorationLineStyle_Single, result.lVal);
|
|
|
|
// Double underline (double supercedes single)
|
|
attr.SetDoublyUnderlined(true);
|
|
updateBuffer(attr);
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result));
|
|
VERIFY_ARE_EQUAL(TextDecorationLineStyle_Double, result.lVal);
|
|
|
|
// Double underline (double on its own)
|
|
attr.SetUnderlined(false);
|
|
updateBuffer(attr);
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result));
|
|
VERIFY_ARE_EQUAL(TextDecorationLineStyle_Double, result.lVal);
|
|
|
|
// No underline
|
|
attr.SetDoublyUnderlined(false);
|
|
updateBuffer(attr);
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result));
|
|
VERIFY_ARE_EQUAL(TextDecorationLineStyle_None, result.lVal);
|
|
}
|
|
{
|
|
Log::Comment(L"Test Font Name (special)");
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_FontNameAttributeId, &result));
|
|
const std::wstring actualFontName{ result.bstrVal };
|
|
const auto expectedFontName{ _pUiaData->GetFontInfo().GetFaceName() };
|
|
VERIFY_ARE_EQUAL(expectedFontName, actualFontName);
|
|
}
|
|
{
|
|
Log::Comment(L"Test Read Only (special)");
|
|
VARIANT result;
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_IsReadOnlyAttributeId, &result));
|
|
VERIFY_IS_FALSE(result.boolVal);
|
|
}
|
|
{
|
|
// "Mixed" is when the desired attribute value is inconsistent across the range.
|
|
// We'll make our life easier by setting an attribute on a character,
|
|
// but getting the attribute for the entire line.
|
|
Log::Comment(L"Test Mixed");
|
|
VARIANT result;
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Line));
|
|
|
|
// set first cell as underlined, but second cell as not underlined
|
|
attr.SetUnderlined(true);
|
|
_pTextBuffer->Write({ attr }, { 0, 0 });
|
|
attr.SetUnderlined(false);
|
|
_pTextBuffer->Write({ attr }, { 1, 0 });
|
|
|
|
VERIFY_SUCCEEDED(utr->GetAttributeValue(UIA_UnderlineStyleAttributeId, &result));
|
|
|
|
// Expected: mixed
|
|
Microsoft::WRL::ComPtr<IUnknown> mixedVal;
|
|
THROW_IF_FAILED(UiaGetReservedMixedAttributeValue(&mixedVal));
|
|
VERIFY_ARE_EQUAL(VT_UNKNOWN, result.vt);
|
|
VERIFY_ARE_EQUAL(mixedVal.Get(), result.punkVal);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(FindAttribute)
|
|
{
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
const COORD startPos{ 0, 0 };
|
|
const COORD endPos{ 0, 2 };
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, startPos, endPos));
|
|
{
|
|
Log::Comment(L"Test Font Name (special)");
|
|
|
|
// Populate query with font name currently in use.
|
|
const auto fontName{ _pUiaData->GetFontInfo().GetFaceName() };
|
|
VARIANT var{};
|
|
var.vt = VT_BSTR;
|
|
var.bstrVal = SysAllocString(fontName.data());
|
|
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> result;
|
|
VERIFY_SUCCEEDED(utr->FindAttribute(UIA_FontNameAttributeId, var, false, result.GetAddressOf()));
|
|
|
|
// Expecting the same text range endpoints
|
|
BOOL isEqual;
|
|
THROW_IF_FAILED(utr->Compare(result.Get(), &isEqual));
|
|
VERIFY_IS_TRUE(isEqual);
|
|
|
|
// Now perform the same test, but searching backwards
|
|
Log::Comment(L"Test Font Name (special) - Backwards");
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> resultBackwards;
|
|
VERIFY_SUCCEEDED(utr->FindAttribute(UIA_FontNameAttributeId, var, true, resultBackwards.GetAddressOf()));
|
|
|
|
// Expecting the same text range endpoints
|
|
THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual));
|
|
VERIFY_IS_TRUE(isEqual);
|
|
}
|
|
{
|
|
Log::Comment(L"Test Read Only (special)");
|
|
|
|
VARIANT var{};
|
|
var.vt = VT_BOOL;
|
|
var.boolVal = false;
|
|
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> result;
|
|
VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsReadOnlyAttributeId, var, false, result.GetAddressOf()));
|
|
|
|
// Expecting the same text range endpoints
|
|
BOOL isEqual;
|
|
THROW_IF_FAILED(utr->Compare(result.Get(), &isEqual));
|
|
VERIFY_IS_TRUE(isEqual);
|
|
|
|
// Now perform the same test, but searching backwards
|
|
Log::Comment(L"Test Read Only (special) - Backwards");
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> resultBackwards;
|
|
VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsReadOnlyAttributeId, var, true, resultBackwards.GetAddressOf()));
|
|
|
|
// Expecting the same text range endpoints
|
|
THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual));
|
|
VERIFY_IS_TRUE(isEqual);
|
|
}
|
|
{
|
|
Log::Comment(L"Test IsItalic (standard attribute)");
|
|
|
|
// Since all of the other attributes operate very similarly,
|
|
// we're just going to pick one of them and test that.
|
|
// The "GetAttribute" tests provide code coverage for
|
|
// retrieving an attribute verification function.
|
|
// This test is intended to provide code coverage for
|
|
// finding a text range with the desired attribute.
|
|
|
|
// Set up the buffer's attributes.
|
|
TextAttribute italicAttr;
|
|
italicAttr.SetItalic(true);
|
|
auto iter{ _pUiaData->GetTextBuffer().GetCellDataAt(startPos) };
|
|
for (auto i = 0; i < 5; ++i)
|
|
{
|
|
_pTextBuffer->Write({ L"X", italicAttr }, iter.Pos());
|
|
++iter;
|
|
}
|
|
|
|
// set the expected end (exclusive)
|
|
const auto expectedEndPos{ iter.Pos() };
|
|
|
|
VARIANT var{};
|
|
var.vt = VT_BOOL;
|
|
var.boolVal = true;
|
|
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> result;
|
|
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(TextUnit_Document));
|
|
VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsItalicAttributeId, var, false, result.GetAddressOf()));
|
|
|
|
Microsoft::WRL::ComPtr<UiaTextRange> resultUtr{ static_cast<UiaTextRange*>(result.Get()) };
|
|
VERIFY_ARE_EQUAL(startPos, resultUtr->_start);
|
|
VERIFY_ARE_EQUAL(expectedEndPos, resultUtr->_end);
|
|
|
|
// Now perform the same test, but searching backwards
|
|
Log::Comment(L"Test IsItalic (standard attribute) - Backwards");
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> resultBackwards;
|
|
VERIFY_SUCCEEDED(utr->FindAttribute(UIA_IsItalicAttributeId, var, true, resultBackwards.GetAddressOf()));
|
|
|
|
// Expecting the same text range endpoints
|
|
BOOL isEqual;
|
|
THROW_IF_FAILED(result->Compare(resultBackwards.Get(), &isEqual));
|
|
VERIFY_IS_TRUE(isEqual);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(BlockRange)
|
|
{
|
|
// This test replicates GH#7960.
|
|
// It was caused by _blockRange being uninitialized, resulting in it occasionally being set to true.
|
|
// Additionally, all of the ctors _except_ the copy ctor initialized it. So this would be more apparent
|
|
// when calling Clone.
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider));
|
|
VERIFY_IS_FALSE(utr->_blockRange);
|
|
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> clone1;
|
|
THROW_IF_FAILED(utr->Clone(&clone1));
|
|
|
|
UiaTextRange* cloneUtr1 = static_cast<UiaTextRange*>(clone1.Get());
|
|
VERIFY_IS_FALSE(cloneUtr1->_blockRange);
|
|
cloneUtr1->_blockRange = true;
|
|
|
|
Microsoft::WRL::ComPtr<ITextRangeProvider> clone2;
|
|
cloneUtr1->Clone(&clone2);
|
|
UiaTextRange* cloneUtr2 = static_cast<UiaTextRange*>(clone2.Get());
|
|
VERIFY_IS_TRUE(cloneUtr2->_blockRange);
|
|
}
|
|
|
|
TEST_METHOD(Movement)
|
|
{
|
|
// Helpful variables
|
|
const auto firstChar{ point_offset_by_char(origin, bufferSize, 1) };
|
|
const auto secondChar{ point_offset_by_char(origin, bufferSize, 2) };
|
|
const auto fifthChar{ point_offset_by_char(origin, bufferSize, 5) };
|
|
const auto sixthChar{ point_offset_by_char(origin, bufferSize, 6) };
|
|
const til::point documentEnd{ bufferSize.left(), (bufferSize.height() / 2) + 1 };
|
|
|
|
// Populate buffer
|
|
// Split the line into 5 segments alternating between "X" and whitespace
|
|
// _________________
|
|
// |XXX XXX XXX|
|
|
// |XXX XXX XXX|
|
|
// |XXX XXX XXX|
|
|
// |XXX XXX XXX|
|
|
// |_______________|
|
|
{
|
|
short i = 0;
|
|
auto iter{ _pTextBuffer->GetCellDataAt(origin) };
|
|
const auto segment{ bufferSize.width() / 5 };
|
|
while (iter.Pos() != documentEnd)
|
|
{
|
|
bool fill{ true };
|
|
if (i % segment == 0)
|
|
{
|
|
fill = !fill;
|
|
}
|
|
|
|
if (fill)
|
|
{
|
|
_pTextBuffer->Write({ L"X" }, iter.Pos());
|
|
}
|
|
|
|
++i;
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
// Define tests
|
|
struct TestInput
|
|
{
|
|
TextUnit unit;
|
|
int moveAmt;
|
|
til::point start;
|
|
til::point end;
|
|
};
|
|
|
|
struct TestOutput
|
|
{
|
|
int moveAmt;
|
|
til::point start;
|
|
til::point end;
|
|
};
|
|
|
|
struct MyTest
|
|
{
|
|
std::wstring name;
|
|
TestInput input;
|
|
TestOutput output;
|
|
};
|
|
|
|
const std::vector<MyTest> tests{
|
|
MyTest{ L"Degenerate at origin", TestInput{ TextUnit_Character, -5, origin, origin }, TestOutput{ 0, origin, origin } }
|
|
};
|
|
}
|
|
|
|
TEST_METHOD(GeneratedMovementTests)
|
|
{
|
|
// Populate the buffer with...
|
|
// - 10 segments of alternating text
|
|
// - up to half of the buffer (vertically)
|
|
// It'll look something like this
|
|
// +------------------------------+
|
|
// |XXX XXX XXX XXX XXX |
|
|
// |XXX XXX XXX XXX XXX |
|
|
// |XXX XXX XXX XXX XXX |
|
|
// |XXX XXX XXX XXX XXX |
|
|
// |XXX XXX XXX XXX XXX |
|
|
// | |
|
|
// | |
|
|
// | |
|
|
// | |
|
|
// | |
|
|
// +------------------------------+
|
|
{
|
|
short i = 0;
|
|
auto iter{ _pTextBuffer->GetCellDataAt(bufferSize.origin()) };
|
|
const auto segment{ bufferSize.width() / 10 };
|
|
bool fill{ true };
|
|
while (iter.Pos() != docEnd)
|
|
{
|
|
if (iter.Pos().X == bufferSize.left())
|
|
{
|
|
fill = true;
|
|
}
|
|
else if (i % segment == 0)
|
|
{
|
|
fill = !fill;
|
|
}
|
|
|
|
_pTextBuffer->Write({ fill ? L"X" : L" " }, iter.Pos());
|
|
|
|
++i;
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"DataSource", L"Export:GeneratedMovementTestDataSource")
|
|
END_TEST_METHOD_PROPERTIES()
|
|
|
|
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
|
WEX::TestExecution::SetVerifyOutput verifyOutputScope{ WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures };
|
|
|
|
unsigned int i{};
|
|
TestData::TryGetValue(L"index", i); // index is produced by the ArrayIndexTaefAdapterSource above
|
|
const auto& testCase{ s_movementTests[i] };
|
|
|
|
Log::Comment(NoThrowString().Format(L"[%zu.0] Test case \"%.*s\"", i, testCase.name.size(), testCase.name.data()));
|
|
if (testCase.skip)
|
|
{
|
|
Log::Result(WEX::Logging::TestResults::Result::Skipped);
|
|
}
|
|
else
|
|
{
|
|
Microsoft::WRL::ComPtr<UiaTextRange> utr;
|
|
int amountMoved;
|
|
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, testCase.input.start, testCase.input.end));
|
|
THROW_IF_FAILED(utr->Move(testCase.input.unit, testCase.input.moveAmount, &amountMoved));
|
|
|
|
VERIFY_ARE_EQUAL(testCase.expected.moveAmount, amountMoved);
|
|
VERIFY_ARE_EQUAL(testCase.expected.start, til::point{ utr->_start });
|
|
VERIFY_ARE_EQUAL(testCase.expected.end, til::point{ utr->_end });
|
|
}
|
|
}
|
|
};
|