Improve UIA movement testing methodology (#10886)

Introduces a new methodology to maintain tests for UI Automation. This includes...
- `UiaTests.csv`: an excel spreadsheet designed to store UIA movement tests in a compact format
- `GeneratedTests.ps1`: a PowerShell script that imports `UiaTests.csv` and outputs a C++ TEST_METHOD for `UiaTextRangeTests.

This new system can be used to easily add more UIA movement tests.

Read https://github.com/microsoft/terminal/blob/dev/cazamor/a11y-7000/testing/tools/TestTableWriter/README.md for more details.

Follow-up work items:
- #10924 **Failing Tests**: this found some failing tests. We should make them not fail.
- #10925 **Missing Tests: Word navigation**: Word navigation is missing.
- #10926 **MoveEndpoint Tests**: an additional column can be added to the CSV "EndpointTarget", which can be "start", "end", or "both". This will allow us to test `MoveEndpoint` in addition to `Move`.
This commit is contained in:
Carlos Zamora 2021-08-19 13:47:07 -07:00 committed by GitHub
parent 482dcec60a
commit 1678b58dde
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 4038 additions and 56 deletions

View file

@ -33,6 +33,7 @@ kje
liga
lje
locl
lorem
maxed
mkmk
mru
@ -59,6 +60,7 @@ TLDR
tokenizes
tonos
tshe
uiatextrange
UIs
und
unregister

View file

@ -11,6 +11,8 @@
#include "../../../buffer/out/textBuffer.hpp"
#include "../types/UiaTracing.h"
#include <IDataSource.h>
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
@ -19,13 +21,193 @@ 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:
@ -197,9 +379,6 @@ class UiaTextRangeTests
TEST_METHOD(DegenerateRangesDetected)
{
const auto bufferSize = _pTextBuffer->GetSize();
const auto origin = bufferSize.Origin();
// make a degenerate range and verify that it reports degenerate
Microsoft::WRL::ComPtr<UiaTextRange> degenerate;
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&degenerate,
@ -211,7 +390,7 @@ class UiaTextRangeTests
VERIFY_ARE_EQUAL(degenerate->_start, degenerate->_end);
// make a non-degenerate range and verify that it reports as such
const COORD end = { origin.X + 1, origin.Y };
const auto end{ point_offset_by_char(origin, bufferSize, 1) };
Microsoft::WRL::ComPtr<UiaTextRange> notDegenerate;
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&notDegenerate,
_pUiaData,
@ -224,9 +403,6 @@ class UiaTextRangeTests
TEST_METHOD(CompareRange)
{
const auto bufferSize = _pTextBuffer->GetSize();
const auto origin = bufferSize.Origin();
Microsoft::WRL::ComPtr<UiaTextRange> utr1;
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr1,
_pUiaData,
@ -244,7 +420,7 @@ class UiaTextRangeTests
VERIFY_IS_TRUE(comparison);
// utr2 redefined to have different end from utr1
const COORD end = { origin.X + 2, origin.Y };
const auto end{ point_offset_by_char(origin, bufferSize, 2) };
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr2,
_pUiaData,
&_dummyProvider,
@ -258,9 +434,6 @@ class UiaTextRangeTests
TEST_METHOD(CompareEndpoints)
{
const auto bufferSize = _pTextBuffer->GetSize();
const auto origin = bufferSize.Origin();
Microsoft::WRL::ComPtr<UiaTextRange> utr1;
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr1,
_pUiaData,
@ -283,7 +456,7 @@ class UiaTextRangeTests
VERIFY_IS_TRUE(comparison == 0);
// utr2 redefined to have different end from utr1
const COORD end = { origin.X + 2, origin.Y };
const auto end{ point_offset_by_char(origin, bufferSize, 2) };
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr2,
_pUiaData,
&_dummyProvider,
@ -465,8 +638,6 @@ class UiaTextRangeTests
start,
end));
const auto bufferSize = _pTextBuffer->GetSize();
const auto origin = bufferSize.Origin();
Microsoft::WRL::ComPtr<UiaTextRange> target;
auto resetTargetUTR = [&]() {
@ -1137,9 +1308,7 @@ class UiaTextRangeTests
// at the end exclusive, the UTR should refuse to move past
// the end.
const auto bufferSize{ _pTextBuffer->GetSize() };
const til::point endInclusive{ bufferSize.RightInclusive(), bufferSize.BottomInclusive() };
const auto endExclusive{ bufferSize.EndExclusive() };
const til::point endInclusive{ bufferEnd };
// Iterate over each TextUnit. If the we don't support
// the given TextUnit, we're supposed to fallback
@ -1153,7 +1322,7 @@ class UiaTextRangeTests
THROW_IF_FAILED(Microsoft::WRL::MakeAndInitialize<UiaTextRange>(&utr, _pUiaData, &_dummyProvider, endInclusive, endExclusive));
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(static_cast<TextUnit>(unit)));
VERIFY_ARE_EQUAL(endExclusive, utr->_end);
VERIFY_ARE_EQUAL(endExclusive, til::point{ utr->_end });
}
}
@ -1162,12 +1331,9 @@ class UiaTextRangeTests
// GH#7663: When attempting to move from end exclusive,
// the UTR should refuse to move past the end.
const auto bufferSize{ _pTextBuffer->GetSize() };
const COORD origin{ bufferSize.Origin() };
const COORD lastLineStart{ bufferSize.Left(), bufferSize.BottomInclusive() };
const COORD secondToLastCharacterPos{ bufferSize.RightInclusive() - 1, bufferSize.BottomInclusive() };
const COORD endInclusive{ bufferSize.RightInclusive(), bufferSize.BottomInclusive() };
const COORD endExclusive{ bufferSize.EndExclusive() };
const auto lastLineStart{ bufferEndLeft };
const auto secondToLastCharacterPos{ point_offset_by_char(bufferEnd, bufferSize, -1) };
const auto endInclusive{ bufferEnd };
// Iterate over each TextUnit. If we don't support
// the given TextUnit, we're supposed to fallback
@ -1198,11 +1364,11 @@ class UiaTextRangeTests
}
THROW_IF_FAILED(utr->Move(textUnit, 1, &moveAmt));
VERIFY_ARE_EQUAL(endExclusive, utr->_end);
VERIFY_ARE_EQUAL(endExclusive, til::point{ utr->_end });
VERIFY_ARE_EQUAL(0, moveAmt);
// write "temp" at (2,2)
const COORD writeTarget{ 2, 2 };
const til::point writeTarget{ 2, 2 };
_pTextBuffer->Write({ L"temp" }, writeTarget);
// Verify expansion works properly
@ -1210,23 +1376,23 @@ class UiaTextRangeTests
THROW_IF_FAILED(utr->ExpandToEnclosingUnit(textUnit));
if (textUnit <= TextUnit::TextUnit_Character)
{
VERIFY_ARE_EQUAL(endInclusive, utr->_start);
VERIFY_ARE_EQUAL(endExclusive, utr->_end);
VERIFY_ARE_EQUAL(endInclusive, til::point{ utr->_start });
VERIFY_ARE_EQUAL(endExclusive, til::point{ utr->_end });
}
else if (textUnit <= TextUnit::TextUnit_Word)
{
VERIFY_ARE_EQUAL(writeTarget, utr->_start);
VERIFY_ARE_EQUAL(endExclusive, utr->_end);
VERIFY_ARE_EQUAL(writeTarget, til::point{ utr->_start });
VERIFY_ARE_EQUAL(endExclusive, til::point{ utr->_end });
}
else if (textUnit <= TextUnit::TextUnit_Line)
{
VERIFY_ARE_EQUAL(lastLineStart, utr->_start);
VERIFY_ARE_EQUAL(endExclusive, utr->_end);
VERIFY_ARE_EQUAL(lastLineStart, til::point{ utr->_start });
VERIFY_ARE_EQUAL(endExclusive, til::point{ utr->_end });
}
else // textUnit <= TextUnit::TextUnit_Document:
{
VERIFY_ARE_EQUAL(origin, utr->_start);
VERIFY_ARE_EQUAL(endExclusive, utr->_end);
VERIFY_ARE_EQUAL(origin, til::point{ utr->_start });
VERIFY_ARE_EQUAL(endExclusive, til::point{ utr->_end });
}
// reset the UTR
@ -1250,23 +1416,23 @@ class UiaTextRangeTests
// 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(degenerate ? endInclusive : secondToLastCharacterPos, utr->_start);
VERIFY_ARE_EQUAL(endInclusive, utr->_end);
VERIFY_ARE_EQUAL(degenerate ? endInclusive : secondToLastCharacterPos, til::point{ utr->_start });
VERIFY_ARE_EQUAL(endInclusive, til::point{ utr->_end });
}
else if (textUnit <= TextUnit::TextUnit_Word)
{
VERIFY_ARE_EQUAL(origin, utr->_start);
VERIFY_ARE_EQUAL(degenerate ? origin : writeTarget, utr->_end);
VERIFY_ARE_EQUAL(origin, til::point{ utr->_start });
VERIFY_ARE_EQUAL(degenerate ? origin : writeTarget, til::point{ utr->_end });
}
else if (textUnit <= TextUnit::TextUnit_Line)
{
VERIFY_ARE_EQUAL(lastLineStart, utr->_start);
VERIFY_ARE_EQUAL(degenerate ? lastLineStart : endExclusive, utr->_end);
VERIFY_ARE_EQUAL(lastLineStart, til::point{ utr->_start });
VERIFY_ARE_EQUAL(degenerate ? lastLineStart : endExclusive, til::point{ utr->_end });
}
else // textUnit <= TextUnit::TextUnit_Document:
{
VERIFY_ARE_EQUAL(origin, utr->_start);
VERIFY_ARE_EQUAL(degenerate ? origin : endExclusive, utr->_end);
VERIFY_ARE_EQUAL(origin, til::point{ utr->_start });
VERIFY_ARE_EQUAL(degenerate ? origin : endExclusive, til::point{ utr->_end });
}
}
@ -1274,9 +1440,7 @@ class UiaTextRangeTests
{
// See GH#7742 for more details.
const auto bufferSize{ _pTextBuffer->GetSize() };
const COORD origin{ bufferSize.Origin() };
const COORD originExclusive{ origin.X, origin.Y + 1 };
const auto originExclusive{ point_offset_by_char(origin, bufferSize, 1) };
_pTextBuffer->Write({ L"My name is Carlos" }, origin);
@ -1314,21 +1478,20 @@ class UiaTextRangeTests
TEST_METHOD(ScrollIntoView)
{
const auto bufferSize{ _pTextBuffer->GetSize() };
const auto viewportSize{ _pUiaData->GetViewport() };
const std::vector<ScrollTest> testData{
{ L"Origin", bufferSize.Top() },
{ L"ViewportHeight From Top - 1", bufferSize.Top() + viewportSize.Height() - 1 },
{ L"ViewportHeight From Top", bufferSize.Top() + viewportSize.Height() },
{ L"ViewportHeight From Top + 1", bufferSize.Top() + viewportSize.Height() + 1 },
{ L"ViewportHeight From Bottom - 1", bufferSize.BottomInclusive() - viewportSize.Height() - 1 },
{ L"ViewportHeight From Bottom", bufferSize.BottomInclusive() - viewportSize.Height() },
{ L"ViewportHeight From Bottom + 1", bufferSize.BottomInclusive() - viewportSize.Height() + 1 },
{ 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", bufferSize.BottomExclusive() }
{ L"Exclusive End", gsl::narrow<short>(bufferSize.bottom()) }
};
BEGIN_TEST_METHOD_PROPERTIES()
@ -1342,7 +1505,7 @@ class UiaTextRangeTests
for (const auto test : testData)
{
Log::Comment(test.comment.c_str());
const til::point pos{ bufferSize.Left(), test.yPos };
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));
}
@ -1663,4 +1826,140 @@ class UiaTextRangeTests
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...
// - 9 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() / 9 };
while (iter.Pos() != docEnd)
{
bool fill{ true };
if (i % segment == 0)
{
fill = !fill;
}
if (fill)
{
_pTextBuffer->Write({ L"X" }, 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 });
}
}
};

View file

@ -0,0 +1,164 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
################################################################################
# This script generates the an array of UiaTextRange tests suitable for replacing the body of
# src\interactivity\win32\ut_interactivity_win32\UiaTextRangeTests.cpp TEST_METHOD(GeneratedMovementTests)
#
# See tools\TestTableWriter\README.md for more details on how to use this script.
[CmdletBinding()]
Param(
[Parameter(Position=0, ValueFromPipeline=$true)]
[string]$TestPath = "UiaTests.csv"
)
# 0. Generate a comment telling people to not modify these tests in the .cpp
$result = "// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// DO NOT MODIFY THESE TESTS DIRECTLY
// These were generated by tools\TestTableWriter\GenerateTests.ps1
// Read tools\TestTableWriter\README.md for more details"
# TODO: THIS IS PROBABLY WRONG. Bottom/Right are exclusive (I think?)
# 1. Define a few helpful variables to make life easier.
$result += "
// Define a few helpful variables
constexpr til::rectangle bufferSize{ 0, 0, 80, 300 };
constexpr short midX{ 40 };
constexpr short midY{ 150 };
constexpr short midPopulatedY{ 75 };
constexpr til::point origin{ 0, 0 };
constexpr til::point midTop{ midX, 0 };
constexpr til::point midHistory{ midX, midPopulatedY };
constexpr til::point midDocEnd{ midX, midY };
constexpr til::point lastCharPos{ 79, midY };
constexpr til::point docEnd{ 0, midY + 1 };
constexpr til::point midEmptySpace{ midX, midY + midPopulatedY };
constexpr til::point bufferEnd{ 79, 299 };
constexpr til::point endExclusive{ 0, 300 };`n"
# 2. Import the CSV test file and find all of the variables we need
$tests = Import-Csv $TestPath;
$vars = New-Object System.Collections.Generic.SortedSet[string];
foreach ($test in $tests)
{
$vars.Add($test.Start) > $null;
$vars.Add($test.End) > $null;
$vars.Add($test.Result_Start) > $null;
$vars.Add($test.Result_End) > $null;
}
# 3. Define each of the vars
# 3.a. Some of the variables were already defined at the beginning. So let's remove those.
$vars.Remove("origin") > $null;
$vars.Remove("midTop") > $null;
$vars.Remove("midHistory") > $null;
$vars.Remove("docEnd") > $null;
# 3.b. Now all of the remaining vars can be deduced from standard vars
foreach ($var in $vars)
{
# Extract the standard var from the name
$standardVar = $var.Contains("Left") ? $var.Split("Left") : $var.Substring(0, $var.length - 3);
# i. Contains number --> requires movement
if ($var -match ".*\d+.*")
{
# 3rd to last character denotes the movement direction
# P --> plus/forwards
# M --> minus/backwards
$moveForward = $var.substring($var.length - 3, 1) -eq 'P';
# 2nd to last character denotes the movement amount
$moveAmt = $var.substring($var.length -2, 1);
# last character denotes the movement type
switch ($var.substring($var.length - 1, 1)) {
'C' # move by character
{
if ($moveForward)
{
$result += "constexpr auto {0}{{ point_offset_by_char({1}, bufferSize, {2}) }};" -f $var, $standardVar, $moveAmt;
}
else
{
$result += "constexpr auto {0}{{ point_offset_by_char({1}, bufferSize, -{2}) }};" -f $var, $standardVar, $moveAmt;
}
}
'L' # move by line
{
if ($moveForward)
{
$result += "constexpr auto {0}{{ point_offset_by_line({1}, bufferSize, {2}) }};" -f $var, $standardVar, $moveAmt;
}
else
{
$result += "constexpr auto {0}{{ point_offset_by_line({1}, bufferSize, -{2}) }};" -f $var, $standardVar, $moveAmt;
}
}
Default { Write-Host "Error: unknown variable movement type" -ForegroundColor Red }
}
}
# ii. Contains "Left" --> set X to left
elseif ($var.Contains("Left"))
{
$result += "constexpr til::point " + $var + "{ bufferSize.left(), " + $standardVar + ".y() };";
}
$result += "`n";
}
# 4. Write the tests
# 4.a. Introduce a struct to store each test as
$result += "struct GeneratedMovementTestInput
{
TextUnit unit;
int moveAmount;
til::point start;
til::point end;
};
struct GeneratedMovementTestExpected
{
int moveAmount;
til::point start;
til::point end;
};
struct GeneratedMovementTest
{
std::wstring_view name;
GeneratedMovementTestInput input;
GeneratedMovementTestExpected expected;
bool skip;
};`n`n";
# 4.b. Iterate through CSV file and generate a test for each one
$result += "static constexpr std::array<GeneratedMovementTest, {0}> s_movementTests`n{{`n" -f $tests.count;
foreach ($test in $tests)
{
$degeneratePrefix = $test.degenerate -eq "TRUE" ? "" : "non-";
$movementType = $test.TextUnit.substring(9);
$testName = "L`"Move {0}degenerate range at position {1} {2} times by {3}`"" -f $degeneratePrefix, $test.Position, $test.MoveAmount, $movementType;
$testInput = "GeneratedMovementTestInput{{
TextUnit::{0},
{1},
{2},
{3}
}}" -f $test.TextUnit, $test.MoveAmount, $test.Start, $test.End;
$testExpected = "GeneratedMovementTestExpected{{
{0},
{1},
{2}
}}" -f $test.Result_MoveAmount, $test.Result_Start, $test.Result_End;
$skip = $test.Skip -eq "TRUE" ? "true" : "false";
$result += " GeneratedMovementTest{{
{0},
{1},
{2},
{3}
}},`n" -f $testName, $testInput, $testExpected, $skip;
}
$result += "};`n`n"
$result > "..\..\src\interactivity\win32\ut_interactivity_win32\GeneratedUiaTextRangeMovementTests.g.cpp";

View file

@ -0,0 +1,90 @@
---
author: Carlos Zamora @carlos-zamora
created on: 2021-08-05
last updated: 2021-08-05
---
# Test Table Writer
The Test Table Writer was written as a method to generate UI Automation tests for OpenConsole. UI Automation has a lot of corner cases, so we developed this workflow to simplify storing and updating these test cases (particularly revolving around movement).
# How to use it
1. Update `UiaTests.csv`:
- This file is used to store the tests in a compact format. The defined columns include...
- Degenerate: is this a degenerate range?
- Position: see the position chart below
- TextUnit: what text unit to move by
- MoveAmount: how many times to move
- Start: the start endpoint of the text range. Represented by a variable name used to signify a position in the buffer. See the variable heuristics section below.
- End: the start endpoint of the text range. Represented by a variable name used to signify a position in the buffer. See the variable heuristics section below.
- Result_MoveAmount: the expected amount to have moved by
- Result_Start: the expected position of the start endpoint after executing the move operation
- Result_End: the expected position of the end endpoint after executing the move operation
- Skip: skip the test. Can be used for failing tests.
- Each row represents a new test in a compact format.
- Use the position chart and the variable heuristics below to add more tests easily.
2. Run `GenerateTests.ps1`
- `GenerateTests.ps1` will load `UiaTests.csv` and export the tests and any necessary variables to "src\interactivity\win32\ut_interactivity_win32\GeneratedUiaTextRangeMovementTests.g.cpp".
3. Build and run the tests
- Build UiaTextRangeTests
- Go to bin\x64\Debug
- Run `clear; .\TE.exe /name:*UiaTextRangeTests*GeneratedMovementTests* .\Conhost.Interactivity.Win32.Unit.Tests.dll` in PowerShell
5. If the tests pass, upload any changes to `UiaTests.csv` and `GeneratedUiaTextRangeMovementTests.g.cpp`. Be sure to run the code formatter.
# Helpful tips
- How to verify a test is authored correctly
- use MS Word to generate some text (try typing "=lorem(5,5)" then pressing enter to generate text)
- use Accessibility Insights to run the UIA API on MS Word's ITextProvider
- if you create a selection (or move the cursor), then tell Accessibility Insights to get the "Selection" range (or refresh it), you can easily set up a test case
- Run the tests via Visual Studio
- In the Solution Explorer, right-click Interactivity.Win32.Tests.Unit and select "Set as Startup Project"
- right-click it again and select "Properties"
- In the Debugging section, set..
- "Command" --> `$(OutDir)/TE.exe`
- "Command Arguments" --> `$(TargetPath) /name:*uiatextrange*generated* /inproc`
# Position chart
The text buffer is assumed to be partially filled. Specifically, the top half of the text buffer contains text, and each row is filled with 9 segments of alternating text. For visualization,
the ascii diagram below shows what the text buffer may look like.
```
+---------------------------+
|1XX XXX X2X XXX XXX|
|XXX XXX XXX XXX XXX|
|XXX XXX X3X XXX XXX|
|XXX XXX XXX XXX XXX|
|XXX XXX X4X XXX XX5|
|6 |
| |
| 7 |
| |
| 8|
+---------------------------+
9
```
The following positions are being tested:
1. `origin`: buffer origin
2. `midTop`: middle of the top line
3. `midHistory`: middle of the history
4. `midDocEnd`: middle of the last line of text
5. `lastCharPos`: last character in the buffer
6. `docEnd`: one past the last line of text
7. `midEmptySpace`: middle of the empty space in the buffer
8. `bufferEnd`: end of the buffer
9. `endExclusive`: exclusive end of the buffer
This is intended to provide adequate testing coverage for GH#6986.
# Variable Heuristics
Each position above already has a predefined variable name. However, a few heuristics are used to define new variables based on the standard variables above.
- `<name>Left`: the left-most position on the same line as `<name>`
- `<name>P<number>C`, `<name>M<number>C`:
- `<name>`: start at the position of `<name>`
- `P` (or `M`): move forwards (aka "plus") by a certain amount (`M` is used to move backwards [aka "minus"])
- `<number>`: how much to move forwards by
- `C`: move by character. For simplicity, assumes that each character is one-cell wide.
- `<name>P<number>L`, `<name>M<number>L`:
- same as above, except move by line. For simplicity, assumes that you won't hit a buffer boundary.
# Helpful terms and concepts
- *degenerate*: the text range encompasses no text. Also, means the start and end endpoints are the same.
- *TextUnit*: a heuristic for how much to move by. Possible values include `TextUnit_Character`, `TextUnit_Word`, and `TextUnit_Line`. See https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-uiautomationtextunits for more details.

View file

@ -0,0 +1,256 @@
Degenerate,Position,TextUnit,MoveAmount,Start,End,Result_MoveAmount,Result_Start,Result_End,Skip
TRUE,1,TextUnit_Character,-5,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Character,-1,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Character,0,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Character,1,origin,origin,1,originP1C,originP1C,FALSE
TRUE,1,TextUnit_Character,5,origin,origin,5,originP5C,originP5C,FALSE
FALSE,1,TextUnit_Character,-5,origin,originP1C,0,origin,originP1C,FALSE
FALSE,1,TextUnit_Character,-1,origin,originP1C,0,origin,originP1C,FALSE
FALSE,1,TextUnit_Character,0,origin,originP1C,0,origin,originP1C,FALSE
FALSE,1,TextUnit_Character,1,origin,originP1C,1,originP1C,originP2C,FALSE
FALSE,1,TextUnit_Character,5,origin,originP1C,5,originP5C,originP6C,FALSE
TRUE,2,TextUnit_Character,-5,midTop,midTop,-5,midTopM5C,midTopM5C,FALSE
TRUE,2,TextUnit_Character,-1,midTop,midTop,-1,midTopM1C,midTopM1C,FALSE
TRUE,2,TextUnit_Character,0,midTop,midTop,0,midTop,midTop,FALSE
TRUE,2,TextUnit_Character,1,midTop,midTop,1,midTopP1C,midTopP1C,FALSE
TRUE,2,TextUnit_Character,5,midTop,midTop,5,midTopP5C,midTopP5C,FALSE
FALSE,2,TextUnit_Character,-5,midTop,midTopP1C,-5,midTopM5C,midTopM4C,FALSE
FALSE,2,TextUnit_Character,-1,midTop,midTopP1C,-1,midTopM1C,midTop,FALSE
FALSE,2,TextUnit_Character,0,midTop,midTopP1C,0,midTop,midTopP1C,FALSE
FALSE,2,TextUnit_Character,1,midTop,midTopP1C,1,midTopP1C,midTopP2C,FALSE
FALSE,2,TextUnit_Character,5,midTop,midTopP1C,5,midTopP5C,midTopP6C,FALSE
TRUE,3,TextUnit_Character,-5,midHistory,midHistory,-5,midHistoryM5C,midHistoryM5C,FALSE
TRUE,3,TextUnit_Character,-1,midHistory,midHistory,-1,midHistoryM1C,midHistoryM1C,FALSE
TRUE,3,TextUnit_Character,0,midHistory,midHistory,0,midHistory,midHistory,FALSE
TRUE,3,TextUnit_Character,1,midHistory,midHistory,1,midHistoryP1C,midHistoryP1C,FALSE
TRUE,3,TextUnit_Character,5,midHistory,midHistory,5,midHistoryP5C,midHistoryP5C,FALSE
FALSE,3,TextUnit_Character,-5,midHistory,midHistoryP1C,-5,midHistoryM5C,midHistoryM4C,FALSE
FALSE,3,TextUnit_Character,-1,midHistory,midHistoryP1C,-1,midHistoryM1C,midHistory,FALSE
FALSE,3,TextUnit_Character,0,midHistory,midHistoryP1C,0,midHistory,midHistoryP1C,FALSE
FALSE,3,TextUnit_Character,1,midHistory,midHistoryP1C,1,midHistoryP1C,midHistoryP2C,FALSE
FALSE,3,TextUnit_Character,5,midHistory,midHistoryP1C,5,midHistoryP5C,midHistoryP6C,FALSE
TRUE,1,TextUnit_Line,-5,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Line,-1,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Line,0,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Line,1,origin,origin,1,originP1L,originP1L,FALSE
TRUE,1,TextUnit_Line,5,origin,origin,5,originP5L,originP5L,FALSE
FALSE,1,TextUnit_Line,-5,origin,originP1C,0,origin,originP1L,TRUE
FALSE,1,TextUnit_Line,-1,origin,originP1C,0,origin,originP1L,TRUE
FALSE,1,TextUnit_Line,0,origin,originP1C,0,origin,originP1L,TRUE
FALSE,1,TextUnit_Line,1,origin,originP1C,1,originP1L,originP2L,FALSE
FALSE,1,TextUnit_Line,5,origin,originP1C,5,originP5L,originP6L,FALSE
TRUE,2,TextUnit_Line,-5,midTop,midTop,-1,origin,origin,TRUE
TRUE,2,TextUnit_Line,-1,midTop,midTop,-1,origin,origin,TRUE
TRUE,2,TextUnit_Line,0,midTop,midTop,0,midTop,midTop,FALSE
TRUE,2,TextUnit_Line,1,midTop,midTop,1,midTopP1L,midTopP1L,FALSE
TRUE,2,TextUnit_Line,5,midTop,midTop,5,midTopP5L,midTopP5L,FALSE
FALSE,2,TextUnit_Line,-5,midTop,midTopP1C,0,origin,originP1L,TRUE
FALSE,2,TextUnit_Line,-1,midTop,midTopP1C,0,origin,originP1L,TRUE
FALSE,2,TextUnit_Line,0,midTop,midTopP1C,0,origin,originP1L,TRUE
FALSE,2,TextUnit_Line,1,midTop,midTopP1C,1,originP1L,originP2L,FALSE
FALSE,2,TextUnit_Line,5,midTop,midTopP1C,5,originP5L,originP6L,FALSE
TRUE,3,TextUnit_Line,-5,midHistory,midHistory,-5,midHistoryM4L,midHistoryM4L,FALSE
TRUE,3,TextUnit_Line,-1,midHistory,midHistory,-1,midHistoryLeft,midHistoryLeft,FALSE
TRUE,3,TextUnit_Line,0,midHistory,midHistory,0,midHistory,midHistory,FALSE
TRUE,3,TextUnit_Line,1,midHistory,midHistory,1,midHistoryP1L,midHistoryP1L,FALSE
TRUE,3,TextUnit_Line,5,midHistory,midHistory,5,midHistoryP5L,midHistoryP5L,FALSE
FALSE,3,TextUnit_Line,-5,midHistory,midHistoryP1C,-5,midHistoryM5L,midHistoryM4L,TRUE
FALSE,3,TextUnit_Line,-1,midHistory,midHistoryP1C,-1,midHistoryM1L,midHistoryLeft,TRUE
FALSE,3,TextUnit_Line,0,midHistory,midHistoryP1C,0,midHistoryLeft,midHistoryP1L,TRUE
FALSE,3,TextUnit_Line,1,midHistory,midHistoryP1C,1,midHistoryP1L,midHistoryP2L,FALSE
FALSE,3,TextUnit_Line,5,midHistory,midHistoryP1C,5,midHistoryP5L,midHistoryP6L,FALSE
TRUE,1,TextUnit_Document,-5,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Document,-1,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Document,0,origin,origin,0,origin,origin,FALSE
TRUE,1,TextUnit_Document,1,origin,origin,1,docEnd,docEnd,TRUE
TRUE,1,TextUnit_Document,5,origin,origin,1,docEnd,docEnd,TRUE
FALSE,1,TextUnit_Document,-5,origin,originP1C,0,origin,docEnd,TRUE
FALSE,1,TextUnit_Document,-1,origin,originP1C,0,origin,docEnd,TRUE
FALSE,1,TextUnit_Document,0,origin,originP1C,0,origin,docEnd,TRUE
FALSE,1,TextUnit_Document,1,origin,originP1C,0,origin,docEnd,TRUE
FALSE,1,TextUnit_Document,5,origin,originP1C,0,origin,docEnd,TRUE
TRUE,2,TextUnit_Document,-5,midTop,midTop,-1,origin,origin,FALSE
TRUE,2,TextUnit_Document,-1,midTop,midTop,-1,origin,origin,FALSE
TRUE,2,TextUnit_Document,0,midTop,midTop,0,midTop,midTop,FALSE
TRUE,2,TextUnit_Document,1,midTop,midTop,1,bufferEnd,bufferEnd,TRUE
TRUE,2,TextUnit_Document,5,midTop,midTop,1,bufferEnd,bufferEnd,TRUE
FALSE,2,TextUnit_Document,-5,midTop,midTopP1C,0,origin,bufferEnd,TRUE
FALSE,2,TextUnit_Document,-1,midTop,midTopP1C,0,origin,bufferEnd,TRUE
FALSE,2,TextUnit_Document,0,midTop,midTopP1C,0,origin,bufferEnd,TRUE
FALSE,2,TextUnit_Document,1,midTop,midTopP1C,0,origin,bufferEnd,TRUE
FALSE,2,TextUnit_Document,5,midTop,midTopP1C,0,origin,bufferEnd,TRUE
TRUE,3,TextUnit_Document,-5,midHistory,midHistory,-1,origin,origin,FALSE
TRUE,3,TextUnit_Document,-1,midHistory,midHistory,-1,origin,origin,FALSE
TRUE,3,TextUnit_Document,0,midHistory,midHistory,0,midHistory,midHistory,FALSE
TRUE,3,TextUnit_Document,1,midHistory,midHistory,1,endExclusive,endExclusive,TRUE
TRUE,3,TextUnit_Document,5,midHistory,midHistory,1,endExclusive,endExclusive,TRUE
FALSE,3,TextUnit_Document,-5,midHistory,midHistoryP1C,0,origin,endExclusive,TRUE
FALSE,3,TextUnit_Document,-1,midHistory,midHistoryP1C,0,origin,endExclusive,TRUE
FALSE,3,TextUnit_Document,0,midHistory,midHistoryP1C,0,origin,endExclusive,TRUE
FALSE,3,TextUnit_Document,1,midHistory,midHistoryP1C,0,origin,endExclusive,TRUE
FALSE,3,TextUnit_Document,5,midHistory,midHistoryP1C,0,origin,endExclusive,TRUE
TRUE,8,TextUnit_Character,-5,bufferEnd,bufferEnd,-5,bufferEndM5C,bufferEndM5C,FALSE
TRUE,8,TextUnit_Character,-1,bufferEnd,bufferEnd,-1,bufferEndM1C,bufferEndM1C,FALSE
TRUE,8,TextUnit_Character,0,bufferEnd,bufferEnd,0,bufferEnd,bufferEnd,FALSE
TRUE,8,TextUnit_Character,1,bufferEnd,bufferEnd,1,endExclusive,endExclusive,TRUE
TRUE,8,TextUnit_Character,5,bufferEnd,bufferEnd,1,endExclusive,endExclusive,TRUE
FALSE,8,TextUnit_Character,-5,bufferEnd,endExclusive,-5,bufferEndM5C,bufferEndM4C,FALSE
FALSE,8,TextUnit_Character,-1,bufferEnd,endExclusive,-1,bufferEndM1C,bufferEnd,FALSE
FALSE,8,TextUnit_Character,0,bufferEnd,endExclusive,0,bufferEnd,endExclusive,FALSE
FALSE,8,TextUnit_Character,1,bufferEnd,endExclusive,0,bufferEnd,endExclusive,FALSE
FALSE,8,TextUnit_Character,5,bufferEnd,endExclusive,0,bufferEnd,endExclusive,FALSE
TRUE,8,TextUnit_Line,-5,bufferEnd,bufferEnd,-5,bufferEndM4L,bufferEndM4L,FALSE
TRUE,8,TextUnit_Line,-1,bufferEnd,bufferEnd,-1,bufferEndLeft,bufferEndLeft,FALSE
TRUE,8,TextUnit_Line,0,bufferEnd,bufferEnd,0,bufferEnd,bufferEnd,FALSE
TRUE,8,TextUnit_Line,1,bufferEnd,bufferEnd,1,endExclusive,endExclusive,TRUE
TRUE,8,TextUnit_Line,5,bufferEnd,bufferEnd,1,endExclusive,endExclusive,TRUE
FALSE,8,TextUnit_Line,-5,bufferEnd,endExclusive,-5,bufferEndM5L,bufferEndM4L,TRUE
FALSE,8,TextUnit_Line,-1,bufferEnd,endExclusive,-1,bufferEndM1L,bufferEndLeft,TRUE
FALSE,8,TextUnit_Line,0,bufferEnd,endExclusive,0,bufferEndLeft,endExclusive,TRUE
FALSE,8,TextUnit_Line,1,bufferEnd,endExclusive,0,bufferEndLeft,endExclusive,TRUE
FALSE,8,TextUnit_Line,5,bufferEnd,endExclusive,0,bufferEndLeft,endExclusive,TRUE
TRUE,8,TextUnit_Document,-5,bufferEnd,bufferEnd,-1,origin,origin,FALSE
TRUE,8,TextUnit_Document,-1,bufferEnd,bufferEnd,-1,origin,origin,FALSE
TRUE,8,TextUnit_Document,0,bufferEnd,bufferEnd,0,bufferEnd,bufferEnd,FALSE
TRUE,8,TextUnit_Document,1,bufferEnd,bufferEnd,1,endExclusive,endExclusive,TRUE
TRUE,8,TextUnit_Document,5,bufferEnd,bufferEnd,1,endExclusive,endExclusive,TRUE
FALSE,8,TextUnit_Document,-5,bufferEnd,endExclusive,0,origin,endExclusive,TRUE
FALSE,8,TextUnit_Document,-1,bufferEnd,endExclusive,0,origin,endExclusive,TRUE
FALSE,8,TextUnit_Document,0,bufferEnd,endExclusive,0,origin,endExclusive,TRUE
FALSE,8,TextUnit_Document,1,bufferEnd,endExclusive,0,origin,endExclusive,TRUE
FALSE,8,TextUnit_Document,5,bufferEnd,endExclusive,0,origin,endExclusive,TRUE
TRUE,9,TextUnit_Character,-5,endExclusive,endExclusive,-5,bufferEndM4C,bufferEndM4C,FALSE
TRUE,9,TextUnit_Character,-1,endExclusive,endExclusive,-1,bufferEnd,bufferEnd,FALSE
TRUE,9,TextUnit_Character,0,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,9,TextUnit_Character,1,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,9,TextUnit_Character,5,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,9,TextUnit_Line,-5,endExclusive,endExclusive,-5,bufferEndM4L,bufferEndM4L,FALSE
TRUE,9,TextUnit_Line,-1,endExclusive,endExclusive,-1,bufferEndLeft,bufferEndLeft,FALSE
TRUE,9,TextUnit_Line,0,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,9,TextUnit_Line,1,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,9,TextUnit_Line,5,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,9,TextUnit_Document,-5,endExclusive,endExclusive,-1,origin,origin,FALSE
TRUE,9,TextUnit_Document,-1,endExclusive,endExclusive,-1,origin,origin,FALSE
TRUE,9,TextUnit_Document,0,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,9,TextUnit_Document,1,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,9,TextUnit_Document,5,endExclusive,endExclusive,0,endExclusive,endExclusive,FALSE
TRUE,4,TextUnit_Character,-5,midDocEnd,midDocEnd,-5,midDocEndM5C,midDocEndM5C,TRUE
TRUE,4,TextUnit_Character,-1,midDocEnd,midDocEnd,-1,midDocEndM1C,midDocEndM1C,TRUE
TRUE,4,TextUnit_Character,0,midDocEnd,midDocEnd,0,midDocEnd,midDocEnd,TRUE
TRUE,4,TextUnit_Character,1,midDocEnd,midDocEnd,1,midDocEndP1C,midDocEndP1C,TRUE
TRUE,4,TextUnit_Character,5,midDocEnd,midDocEnd,5,midDocEndP5C,midDocEndP5C,TRUE
FALSE,4,TextUnit_Character,-5,midDocEnd,midDocEndP1C,-5,midDocEndM5C,midDocEndM4C,TRUE
FALSE,4,TextUnit_Character,-1,midDocEnd,midDocEndP1C,-1,midDocEndM1C,midDocEnd,TRUE
FALSE,4,TextUnit_Character,0,midDocEnd,midDocEndP1C,0,midDocEnd,midDocEndP1C,TRUE
FALSE,4,TextUnit_Character,1,midDocEnd,midDocEndP1C,1,midDocEndP1C,midDocEndP2C,TRUE
FALSE,4,TextUnit_Character,5,midDocEnd,midDocEndP1C,5,midDocEndP5C,midDocEndP6C,TRUE
TRUE,4,TextUnit_Line,-5,midDocEnd,midDocEnd,-5,midDocEndM4L,midDocEndM4L,TRUE
TRUE,4,TextUnit_Line,-1,midDocEnd,midDocEnd,-1,midDocEndLeft,midDocEndLeft,TRUE
TRUE,4,TextUnit_Line,0,midDocEnd,midDocEnd,0,midDocEnd,midDocEnd,TRUE
TRUE,4,TextUnit_Line,1,midDocEnd,midDocEnd,1,docEnd,docEnd,TRUE
TRUE,4,TextUnit_Line,5,midDocEnd,midDocEnd,1,docEnd,docEnd,TRUE
FALSE,4,TextUnit_Line,-5,midDocEnd,midDocEndP1C,-5,midDocEndM5L,midDocEndM4L,TRUE
FALSE,4,TextUnit_Line,-1,midDocEnd,midDocEndP1C,-1,midDocEndM1L,midDocEndLeft,TRUE
FALSE,4,TextUnit_Line,0,midDocEnd,midDocEndP1C,0,midDocEndLeft,docEnd,TRUE
FALSE,4,TextUnit_Line,1,midDocEnd,midDocEndP1C,0,midDocEndLeft,docEnd,TRUE
FALSE,4,TextUnit_Line,5,midDocEnd,midDocEndP1C,0,midDocEndLeft,docEnd,TRUE
TRUE,4,TextUnit_Document,-5,midDocEnd,midDocEnd,-1,origin,origin,TRUE
TRUE,4,TextUnit_Document,-1,midDocEnd,midDocEnd,-1,origin,origin,TRUE
TRUE,4,TextUnit_Document,0,midDocEnd,midDocEnd,0,midDocEnd,midDocEnd,TRUE
TRUE,4,TextUnit_Document,1,midDocEnd,midDocEnd,1,docEnd,docEnd,TRUE
TRUE,4,TextUnit_Document,5,midDocEnd,midDocEnd,1,docEnd,docEnd,TRUE
FALSE,4,TextUnit_Document,-5,midDocEnd,midDocEndP1C,0,origin,docEnd,TRUE
FALSE,4,TextUnit_Document,-1,midDocEnd,midDocEndP1C,0,origin,docEnd,TRUE
FALSE,4,TextUnit_Document,0,midDocEnd,midDocEndP1C,0,origin,docEnd,TRUE
FALSE,4,TextUnit_Document,1,midDocEnd,midDocEndP1C,0,origin,docEnd,TRUE
FALSE,4,TextUnit_Document,5,midDocEnd,midDocEndP1C,0,origin,docEnd,TRUE
TRUE,5,TextUnit_Character,-5,lastCharPos,lastCharPos,-5,lastCharPosM5C,lastCharPosM5C,TRUE
TRUE,5,TextUnit_Character,-1,lastCharPos,lastCharPos,-1,lastCharPosM1C,lastCharPosM1C,TRUE
TRUE,5,TextUnit_Character,0,lastCharPos,lastCharPos,0,lastCharPos,lastCharPos,TRUE
TRUE,5,TextUnit_Character,1,lastCharPos,lastCharPos,1,docEnd,docEnd,TRUE
TRUE,5,TextUnit_Character,5,lastCharPos,lastCharPos,1,docEnd,docEnd,TRUE
FALSE,5,TextUnit_Character,-5,lastCharPos,lastCharPosP1C,-5,lastCharPosM5C,lastCharPosM4C,TRUE
FALSE,5,TextUnit_Character,-1,lastCharPos,lastCharPosP1C,-1,lastCharPosM1C,lastCharPos,TRUE
FALSE,5,TextUnit_Character,0,lastCharPos,lastCharPosP1C,0,lastCharPos,docEnd,TRUE
FALSE,5,TextUnit_Character,1,lastCharPos,lastCharPosP1C,0,lastCharPos,docEnd,TRUE
FALSE,5,TextUnit_Character,5,lastCharPos,lastCharPosP1C,0,lastCharPos,docEnd,TRUE
TRUE,5,TextUnit_Line,-5,lastCharPos,lastCharPos,-5,lastCharPosM4L,lastCharPosM4L,TRUE
TRUE,5,TextUnit_Line,-1,lastCharPos,lastCharPos,-1,lastCharPosLeft,lastCharPosLeft,TRUE
TRUE,5,TextUnit_Line,0,lastCharPos,lastCharPos,0,lastCharPos,lastCharPos,TRUE
TRUE,5,TextUnit_Line,1,lastCharPos,lastCharPos,1,docEnd,docEnd,TRUE
TRUE,5,TextUnit_Line,5,lastCharPos,lastCharPos,1,docEnd,docEnd,TRUE
FALSE,5,TextUnit_Line,-5,lastCharPos,lastCharPosP1C,-5,lastCharPosM5L,lastCharPosM4L,TRUE
FALSE,5,TextUnit_Line,-1,lastCharPos,lastCharPosP1C,-1,lastCharPosM1L,lastCharPosLeft,TRUE
FALSE,5,TextUnit_Line,0,lastCharPos,lastCharPosP1C,0,lastCharPosLeft,docEnd,TRUE
FALSE,5,TextUnit_Line,1,lastCharPos,lastCharPosP1C,0,lastCharPosLeft,docEnd,TRUE
FALSE,5,TextUnit_Line,5,lastCharPos,lastCharPosP1C,0,lastCharPosLeft,docEnd,TRUE
TRUE,5,TextUnit_Document,-5,lastCharPos,lastCharPos,-1,origin,origin,TRUE
TRUE,5,TextUnit_Document,-1,lastCharPos,lastCharPos,-1,origin,origin,TRUE
TRUE,5,TextUnit_Document,0,lastCharPos,lastCharPos,0,lastCharPos,lastCharPos,TRUE
TRUE,5,TextUnit_Document,1,lastCharPos,lastCharPos,1,docEnd,docEnd,TRUE
TRUE,5,TextUnit_Document,5,lastCharPos,lastCharPos,1,docEnd,docEnd,TRUE
FALSE,5,TextUnit_Document,-5,lastCharPos,lastCharPosP1C,0,origin,docEnd,TRUE
FALSE,5,TextUnit_Document,-1,lastCharPos,lastCharPosP1C,0,origin,docEnd,TRUE
FALSE,5,TextUnit_Document,0,lastCharPos,lastCharPosP1C,0,origin,docEnd,TRUE
FALSE,5,TextUnit_Document,1,lastCharPos,lastCharPosP1C,0,origin,docEnd,TRUE
FALSE,5,TextUnit_Document,5,lastCharPos,lastCharPosP1C,0,origin,docEnd,TRUE
TRUE,6,TextUnit_Character,-5,docEnd,docEnd,-5,docEndM5C,docEndM5C,TRUE
TRUE,6,TextUnit_Character,-1,docEnd,docEnd,-1,docEndM1C,docEndM1C,TRUE
TRUE,6,TextUnit_Character,0,docEnd,docEnd,0,docEnd,docEnd,TRUE
TRUE,6,TextUnit_Character,1,docEnd,docEnd,0,docEnd,docEnd,TRUE
TRUE,6,TextUnit_Character,5,docEnd,docEnd,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Character,-5,docEnd,docEndP1C,-5,docEndM5C,docEndM5C,TRUE
FALSE,6,TextUnit_Character,-1,docEnd,docEndP1C,-1,docEndM1C,docEndM1C,TRUE
FALSE,6,TextUnit_Character,0,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Character,1,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Character,5,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
TRUE,6,TextUnit_Line,-5,docEnd,docEnd,-5,docEndM4L,docEndM4L,TRUE
TRUE,6,TextUnit_Line,-1,docEnd,docEnd,-1,docEndLeft,docEndLeft,TRUE
TRUE,6,TextUnit_Line,0,docEnd,docEnd,0,docEnd,docEnd,TRUE
TRUE,6,TextUnit_Line,1,docEnd,docEnd,0,docEnd,docEnd,TRUE
TRUE,6,TextUnit_Line,5,docEnd,docEnd,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Line,-5,docEnd,docEndP1C,-5,docEndM4L,docEndM4L,TRUE
FALSE,6,TextUnit_Line,-1,docEnd,docEndP1C,-1,docEndLeft,docEndLeft,TRUE
FALSE,6,TextUnit_Line,0,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Line,1,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Line,5,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
TRUE,6,TextUnit_Document,-5,docEnd,docEnd,-1,origin,origin,TRUE
TRUE,6,TextUnit_Document,-1,docEnd,docEnd,-1,origin,origin,TRUE
TRUE,6,TextUnit_Document,0,docEnd,docEnd,0,docEnd,docEnd,TRUE
TRUE,6,TextUnit_Document,1,docEnd,docEnd,0,docEnd,docEnd,TRUE
TRUE,6,TextUnit_Document,5,docEnd,docEnd,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Document,-5,docEnd,docEndP1C,-1,origin,origin,TRUE
FALSE,6,TextUnit_Document,-1,docEnd,docEndP1C,-1,origin,origin,TRUE
FALSE,6,TextUnit_Document,0,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Document,1,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
FALSE,6,TextUnit_Document,5,docEnd,docEndP1C,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Character,-5,midEmptySpace,midEmptySpace,-5,docEndM5C,docEndM5C,TRUE
TRUE,7,TextUnit_Character,-1,midEmptySpace,midEmptySpace,-1,docEndM1C,docEndM1C,TRUE
TRUE,7,TextUnit_Character,0,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Character,1,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Character,5,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Character,-5,midEmptySpace,midEmptySpaceP1C,-5,docEndM5C,docEndM5C,TRUE
FALSE,7,TextUnit_Character,-1,midEmptySpace,midEmptySpaceP1C,-1,docEndM1C,docEndM1C,TRUE
FALSE,7,TextUnit_Character,0,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Character,1,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Character,5,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Line,-5,midEmptySpace,midEmptySpace,-5,docEndM4L,docEndM4L,TRUE
TRUE,7,TextUnit_Line,-1,midEmptySpace,midEmptySpace,-1,docEndLeft,docEndLeft,TRUE
TRUE,7,TextUnit_Line,0,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Line,1,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Line,5,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Line,-5,midEmptySpace,midEmptySpaceP1C,-5,docEndM4L,docEndM4L,TRUE
FALSE,7,TextUnit_Line,-1,midEmptySpace,midEmptySpaceP1C,-1,docEndLeft,docEndLeft,TRUE
FALSE,7,TextUnit_Line,0,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Line,1,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Line,5,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Document,-5,midEmptySpace,midEmptySpace,-1,origin,origin,TRUE
TRUE,7,TextUnit_Document,-1,midEmptySpace,midEmptySpace,-1,origin,origin,TRUE
TRUE,7,TextUnit_Document,0,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Document,1,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
TRUE,7,TextUnit_Document,5,midEmptySpace,midEmptySpace,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Document,-5,midEmptySpace,midEmptySpaceP1C,-1,origin,origin,TRUE
FALSE,7,TextUnit_Document,-1,midEmptySpace,midEmptySpaceP1C,-1,origin,origin,TRUE
FALSE,7,TextUnit_Document,0,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Document,1,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
FALSE,7,TextUnit_Document,5,midEmptySpace,midEmptySpaceP1C,0,docEnd,docEnd,TRUE
1 Degenerate Position TextUnit MoveAmount Start End Result_MoveAmount Result_Start Result_End Skip
2 TRUE 1 TextUnit_Character -5 origin origin 0 origin origin FALSE
3 TRUE 1 TextUnit_Character -1 origin origin 0 origin origin FALSE
4 TRUE 1 TextUnit_Character 0 origin origin 0 origin origin FALSE
5 TRUE 1 TextUnit_Character 1 origin origin 1 originP1C originP1C FALSE
6 TRUE 1 TextUnit_Character 5 origin origin 5 originP5C originP5C FALSE
7 FALSE 1 TextUnit_Character -5 origin originP1C 0 origin originP1C FALSE
8 FALSE 1 TextUnit_Character -1 origin originP1C 0 origin originP1C FALSE
9 FALSE 1 TextUnit_Character 0 origin originP1C 0 origin originP1C FALSE
10 FALSE 1 TextUnit_Character 1 origin originP1C 1 originP1C originP2C FALSE
11 FALSE 1 TextUnit_Character 5 origin originP1C 5 originP5C originP6C FALSE
12 TRUE 2 TextUnit_Character -5 midTop midTop -5 midTopM5C midTopM5C FALSE
13 TRUE 2 TextUnit_Character -1 midTop midTop -1 midTopM1C midTopM1C FALSE
14 TRUE 2 TextUnit_Character 0 midTop midTop 0 midTop midTop FALSE
15 TRUE 2 TextUnit_Character 1 midTop midTop 1 midTopP1C midTopP1C FALSE
16 TRUE 2 TextUnit_Character 5 midTop midTop 5 midTopP5C midTopP5C FALSE
17 FALSE 2 TextUnit_Character -5 midTop midTopP1C -5 midTopM5C midTopM4C FALSE
18 FALSE 2 TextUnit_Character -1 midTop midTopP1C -1 midTopM1C midTop FALSE
19 FALSE 2 TextUnit_Character 0 midTop midTopP1C 0 midTop midTopP1C FALSE
20 FALSE 2 TextUnit_Character 1 midTop midTopP1C 1 midTopP1C midTopP2C FALSE
21 FALSE 2 TextUnit_Character 5 midTop midTopP1C 5 midTopP5C midTopP6C FALSE
22 TRUE 3 TextUnit_Character -5 midHistory midHistory -5 midHistoryM5C midHistoryM5C FALSE
23 TRUE 3 TextUnit_Character -1 midHistory midHistory -1 midHistoryM1C midHistoryM1C FALSE
24 TRUE 3 TextUnit_Character 0 midHistory midHistory 0 midHistory midHistory FALSE
25 TRUE 3 TextUnit_Character 1 midHistory midHistory 1 midHistoryP1C midHistoryP1C FALSE
26 TRUE 3 TextUnit_Character 5 midHistory midHistory 5 midHistoryP5C midHistoryP5C FALSE
27 FALSE 3 TextUnit_Character -5 midHistory midHistoryP1C -5 midHistoryM5C midHistoryM4C FALSE
28 FALSE 3 TextUnit_Character -1 midHistory midHistoryP1C -1 midHistoryM1C midHistory FALSE
29 FALSE 3 TextUnit_Character 0 midHistory midHistoryP1C 0 midHistory midHistoryP1C FALSE
30 FALSE 3 TextUnit_Character 1 midHistory midHistoryP1C 1 midHistoryP1C midHistoryP2C FALSE
31 FALSE 3 TextUnit_Character 5 midHistory midHistoryP1C 5 midHistoryP5C midHistoryP6C FALSE
32 TRUE 1 TextUnit_Line -5 origin origin 0 origin origin FALSE
33 TRUE 1 TextUnit_Line -1 origin origin 0 origin origin FALSE
34 TRUE 1 TextUnit_Line 0 origin origin 0 origin origin FALSE
35 TRUE 1 TextUnit_Line 1 origin origin 1 originP1L originP1L FALSE
36 TRUE 1 TextUnit_Line 5 origin origin 5 originP5L originP5L FALSE
37 FALSE 1 TextUnit_Line -5 origin originP1C 0 origin originP1L TRUE
38 FALSE 1 TextUnit_Line -1 origin originP1C 0 origin originP1L TRUE
39 FALSE 1 TextUnit_Line 0 origin originP1C 0 origin originP1L TRUE
40 FALSE 1 TextUnit_Line 1 origin originP1C 1 originP1L originP2L FALSE
41 FALSE 1 TextUnit_Line 5 origin originP1C 5 originP5L originP6L FALSE
42 TRUE 2 TextUnit_Line -5 midTop midTop -1 origin origin TRUE
43 TRUE 2 TextUnit_Line -1 midTop midTop -1 origin origin TRUE
44 TRUE 2 TextUnit_Line 0 midTop midTop 0 midTop midTop FALSE
45 TRUE 2 TextUnit_Line 1 midTop midTop 1 midTopP1L midTopP1L FALSE
46 TRUE 2 TextUnit_Line 5 midTop midTop 5 midTopP5L midTopP5L FALSE
47 FALSE 2 TextUnit_Line -5 midTop midTopP1C 0 origin originP1L TRUE
48 FALSE 2 TextUnit_Line -1 midTop midTopP1C 0 origin originP1L TRUE
49 FALSE 2 TextUnit_Line 0 midTop midTopP1C 0 origin originP1L TRUE
50 FALSE 2 TextUnit_Line 1 midTop midTopP1C 1 originP1L originP2L FALSE
51 FALSE 2 TextUnit_Line 5 midTop midTopP1C 5 originP5L originP6L FALSE
52 TRUE 3 TextUnit_Line -5 midHistory midHistory -5 midHistoryM4L midHistoryM4L FALSE
53 TRUE 3 TextUnit_Line -1 midHistory midHistory -1 midHistoryLeft midHistoryLeft FALSE
54 TRUE 3 TextUnit_Line 0 midHistory midHistory 0 midHistory midHistory FALSE
55 TRUE 3 TextUnit_Line 1 midHistory midHistory 1 midHistoryP1L midHistoryP1L FALSE
56 TRUE 3 TextUnit_Line 5 midHistory midHistory 5 midHistoryP5L midHistoryP5L FALSE
57 FALSE 3 TextUnit_Line -5 midHistory midHistoryP1C -5 midHistoryM5L midHistoryM4L TRUE
58 FALSE 3 TextUnit_Line -1 midHistory midHistoryP1C -1 midHistoryM1L midHistoryLeft TRUE
59 FALSE 3 TextUnit_Line 0 midHistory midHistoryP1C 0 midHistoryLeft midHistoryP1L TRUE
60 FALSE 3 TextUnit_Line 1 midHistory midHistoryP1C 1 midHistoryP1L midHistoryP2L FALSE
61 FALSE 3 TextUnit_Line 5 midHistory midHistoryP1C 5 midHistoryP5L midHistoryP6L FALSE
62 TRUE 1 TextUnit_Document -5 origin origin 0 origin origin FALSE
63 TRUE 1 TextUnit_Document -1 origin origin 0 origin origin FALSE
64 TRUE 1 TextUnit_Document 0 origin origin 0 origin origin FALSE
65 TRUE 1 TextUnit_Document 1 origin origin 1 docEnd docEnd TRUE
66 TRUE 1 TextUnit_Document 5 origin origin 1 docEnd docEnd TRUE
67 FALSE 1 TextUnit_Document -5 origin originP1C 0 origin docEnd TRUE
68 FALSE 1 TextUnit_Document -1 origin originP1C 0 origin docEnd TRUE
69 FALSE 1 TextUnit_Document 0 origin originP1C 0 origin docEnd TRUE
70 FALSE 1 TextUnit_Document 1 origin originP1C 0 origin docEnd TRUE
71 FALSE 1 TextUnit_Document 5 origin originP1C 0 origin docEnd TRUE
72 TRUE 2 TextUnit_Document -5 midTop midTop -1 origin origin FALSE
73 TRUE 2 TextUnit_Document -1 midTop midTop -1 origin origin FALSE
74 TRUE 2 TextUnit_Document 0 midTop midTop 0 midTop midTop FALSE
75 TRUE 2 TextUnit_Document 1 midTop midTop 1 bufferEnd bufferEnd TRUE
76 TRUE 2 TextUnit_Document 5 midTop midTop 1 bufferEnd bufferEnd TRUE
77 FALSE 2 TextUnit_Document -5 midTop midTopP1C 0 origin bufferEnd TRUE
78 FALSE 2 TextUnit_Document -1 midTop midTopP1C 0 origin bufferEnd TRUE
79 FALSE 2 TextUnit_Document 0 midTop midTopP1C 0 origin bufferEnd TRUE
80 FALSE 2 TextUnit_Document 1 midTop midTopP1C 0 origin bufferEnd TRUE
81 FALSE 2 TextUnit_Document 5 midTop midTopP1C 0 origin bufferEnd TRUE
82 TRUE 3 TextUnit_Document -5 midHistory midHistory -1 origin origin FALSE
83 TRUE 3 TextUnit_Document -1 midHistory midHistory -1 origin origin FALSE
84 TRUE 3 TextUnit_Document 0 midHistory midHistory 0 midHistory midHistory FALSE
85 TRUE 3 TextUnit_Document 1 midHistory midHistory 1 endExclusive endExclusive TRUE
86 TRUE 3 TextUnit_Document 5 midHistory midHistory 1 endExclusive endExclusive TRUE
87 FALSE 3 TextUnit_Document -5 midHistory midHistoryP1C 0 origin endExclusive TRUE
88 FALSE 3 TextUnit_Document -1 midHistory midHistoryP1C 0 origin endExclusive TRUE
89 FALSE 3 TextUnit_Document 0 midHistory midHistoryP1C 0 origin endExclusive TRUE
90 FALSE 3 TextUnit_Document 1 midHistory midHistoryP1C 0 origin endExclusive TRUE
91 FALSE 3 TextUnit_Document 5 midHistory midHistoryP1C 0 origin endExclusive TRUE
92 TRUE 8 TextUnit_Character -5 bufferEnd bufferEnd -5 bufferEndM5C bufferEndM5C FALSE
93 TRUE 8 TextUnit_Character -1 bufferEnd bufferEnd -1 bufferEndM1C bufferEndM1C FALSE
94 TRUE 8 TextUnit_Character 0 bufferEnd bufferEnd 0 bufferEnd bufferEnd FALSE
95 TRUE 8 TextUnit_Character 1 bufferEnd bufferEnd 1 endExclusive endExclusive TRUE
96 TRUE 8 TextUnit_Character 5 bufferEnd bufferEnd 1 endExclusive endExclusive TRUE
97 FALSE 8 TextUnit_Character -5 bufferEnd endExclusive -5 bufferEndM5C bufferEndM4C FALSE
98 FALSE 8 TextUnit_Character -1 bufferEnd endExclusive -1 bufferEndM1C bufferEnd FALSE
99 FALSE 8 TextUnit_Character 0 bufferEnd endExclusive 0 bufferEnd endExclusive FALSE
100 FALSE 8 TextUnit_Character 1 bufferEnd endExclusive 0 bufferEnd endExclusive FALSE
101 FALSE 8 TextUnit_Character 5 bufferEnd endExclusive 0 bufferEnd endExclusive FALSE
102 TRUE 8 TextUnit_Line -5 bufferEnd bufferEnd -5 bufferEndM4L bufferEndM4L FALSE
103 TRUE 8 TextUnit_Line -1 bufferEnd bufferEnd -1 bufferEndLeft bufferEndLeft FALSE
104 TRUE 8 TextUnit_Line 0 bufferEnd bufferEnd 0 bufferEnd bufferEnd FALSE
105 TRUE 8 TextUnit_Line 1 bufferEnd bufferEnd 1 endExclusive endExclusive TRUE
106 TRUE 8 TextUnit_Line 5 bufferEnd bufferEnd 1 endExclusive endExclusive TRUE
107 FALSE 8 TextUnit_Line -5 bufferEnd endExclusive -5 bufferEndM5L bufferEndM4L TRUE
108 FALSE 8 TextUnit_Line -1 bufferEnd endExclusive -1 bufferEndM1L bufferEndLeft TRUE
109 FALSE 8 TextUnit_Line 0 bufferEnd endExclusive 0 bufferEndLeft endExclusive TRUE
110 FALSE 8 TextUnit_Line 1 bufferEnd endExclusive 0 bufferEndLeft endExclusive TRUE
111 FALSE 8 TextUnit_Line 5 bufferEnd endExclusive 0 bufferEndLeft endExclusive TRUE
112 TRUE 8 TextUnit_Document -5 bufferEnd bufferEnd -1 origin origin FALSE
113 TRUE 8 TextUnit_Document -1 bufferEnd bufferEnd -1 origin origin FALSE
114 TRUE 8 TextUnit_Document 0 bufferEnd bufferEnd 0 bufferEnd bufferEnd FALSE
115 TRUE 8 TextUnit_Document 1 bufferEnd bufferEnd 1 endExclusive endExclusive TRUE
116 TRUE 8 TextUnit_Document 5 bufferEnd bufferEnd 1 endExclusive endExclusive TRUE
117 FALSE 8 TextUnit_Document -5 bufferEnd endExclusive 0 origin endExclusive TRUE
118 FALSE 8 TextUnit_Document -1 bufferEnd endExclusive 0 origin endExclusive TRUE
119 FALSE 8 TextUnit_Document 0 bufferEnd endExclusive 0 origin endExclusive TRUE
120 FALSE 8 TextUnit_Document 1 bufferEnd endExclusive 0 origin endExclusive TRUE
121 FALSE 8 TextUnit_Document 5 bufferEnd endExclusive 0 origin endExclusive TRUE
122 TRUE 9 TextUnit_Character -5 endExclusive endExclusive -5 bufferEndM4C bufferEndM4C FALSE
123 TRUE 9 TextUnit_Character -1 endExclusive endExclusive -1 bufferEnd bufferEnd FALSE
124 TRUE 9 TextUnit_Character 0 endExclusive endExclusive 0 endExclusive endExclusive FALSE
125 TRUE 9 TextUnit_Character 1 endExclusive endExclusive 0 endExclusive endExclusive FALSE
126 TRUE 9 TextUnit_Character 5 endExclusive endExclusive 0 endExclusive endExclusive FALSE
127 TRUE 9 TextUnit_Line -5 endExclusive endExclusive -5 bufferEndM4L bufferEndM4L FALSE
128 TRUE 9 TextUnit_Line -1 endExclusive endExclusive -1 bufferEndLeft bufferEndLeft FALSE
129 TRUE 9 TextUnit_Line 0 endExclusive endExclusive 0 endExclusive endExclusive FALSE
130 TRUE 9 TextUnit_Line 1 endExclusive endExclusive 0 endExclusive endExclusive FALSE
131 TRUE 9 TextUnit_Line 5 endExclusive endExclusive 0 endExclusive endExclusive FALSE
132 TRUE 9 TextUnit_Document -5 endExclusive endExclusive -1 origin origin FALSE
133 TRUE 9 TextUnit_Document -1 endExclusive endExclusive -1 origin origin FALSE
134 TRUE 9 TextUnit_Document 0 endExclusive endExclusive 0 endExclusive endExclusive FALSE
135 TRUE 9 TextUnit_Document 1 endExclusive endExclusive 0 endExclusive endExclusive FALSE
136 TRUE 9 TextUnit_Document 5 endExclusive endExclusive 0 endExclusive endExclusive FALSE
137 TRUE 4 TextUnit_Character -5 midDocEnd midDocEnd -5 midDocEndM5C midDocEndM5C TRUE
138 TRUE 4 TextUnit_Character -1 midDocEnd midDocEnd -1 midDocEndM1C midDocEndM1C TRUE
139 TRUE 4 TextUnit_Character 0 midDocEnd midDocEnd 0 midDocEnd midDocEnd TRUE
140 TRUE 4 TextUnit_Character 1 midDocEnd midDocEnd 1 midDocEndP1C midDocEndP1C TRUE
141 TRUE 4 TextUnit_Character 5 midDocEnd midDocEnd 5 midDocEndP5C midDocEndP5C TRUE
142 FALSE 4 TextUnit_Character -5 midDocEnd midDocEndP1C -5 midDocEndM5C midDocEndM4C TRUE
143 FALSE 4 TextUnit_Character -1 midDocEnd midDocEndP1C -1 midDocEndM1C midDocEnd TRUE
144 FALSE 4 TextUnit_Character 0 midDocEnd midDocEndP1C 0 midDocEnd midDocEndP1C TRUE
145 FALSE 4 TextUnit_Character 1 midDocEnd midDocEndP1C 1 midDocEndP1C midDocEndP2C TRUE
146 FALSE 4 TextUnit_Character 5 midDocEnd midDocEndP1C 5 midDocEndP5C midDocEndP6C TRUE
147 TRUE 4 TextUnit_Line -5 midDocEnd midDocEnd -5 midDocEndM4L midDocEndM4L TRUE
148 TRUE 4 TextUnit_Line -1 midDocEnd midDocEnd -1 midDocEndLeft midDocEndLeft TRUE
149 TRUE 4 TextUnit_Line 0 midDocEnd midDocEnd 0 midDocEnd midDocEnd TRUE
150 TRUE 4 TextUnit_Line 1 midDocEnd midDocEnd 1 docEnd docEnd TRUE
151 TRUE 4 TextUnit_Line 5 midDocEnd midDocEnd 1 docEnd docEnd TRUE
152 FALSE 4 TextUnit_Line -5 midDocEnd midDocEndP1C -5 midDocEndM5L midDocEndM4L TRUE
153 FALSE 4 TextUnit_Line -1 midDocEnd midDocEndP1C -1 midDocEndM1L midDocEndLeft TRUE
154 FALSE 4 TextUnit_Line 0 midDocEnd midDocEndP1C 0 midDocEndLeft docEnd TRUE
155 FALSE 4 TextUnit_Line 1 midDocEnd midDocEndP1C 0 midDocEndLeft docEnd TRUE
156 FALSE 4 TextUnit_Line 5 midDocEnd midDocEndP1C 0 midDocEndLeft docEnd TRUE
157 TRUE 4 TextUnit_Document -5 midDocEnd midDocEnd -1 origin origin TRUE
158 TRUE 4 TextUnit_Document -1 midDocEnd midDocEnd -1 origin origin TRUE
159 TRUE 4 TextUnit_Document 0 midDocEnd midDocEnd 0 midDocEnd midDocEnd TRUE
160 TRUE 4 TextUnit_Document 1 midDocEnd midDocEnd 1 docEnd docEnd TRUE
161 TRUE 4 TextUnit_Document 5 midDocEnd midDocEnd 1 docEnd docEnd TRUE
162 FALSE 4 TextUnit_Document -5 midDocEnd midDocEndP1C 0 origin docEnd TRUE
163 FALSE 4 TextUnit_Document -1 midDocEnd midDocEndP1C 0 origin docEnd TRUE
164 FALSE 4 TextUnit_Document 0 midDocEnd midDocEndP1C 0 origin docEnd TRUE
165 FALSE 4 TextUnit_Document 1 midDocEnd midDocEndP1C 0 origin docEnd TRUE
166 FALSE 4 TextUnit_Document 5 midDocEnd midDocEndP1C 0 origin docEnd TRUE
167 TRUE 5 TextUnit_Character -5 lastCharPos lastCharPos -5 lastCharPosM5C lastCharPosM5C TRUE
168 TRUE 5 TextUnit_Character -1 lastCharPos lastCharPos -1 lastCharPosM1C lastCharPosM1C TRUE
169 TRUE 5 TextUnit_Character 0 lastCharPos lastCharPos 0 lastCharPos lastCharPos TRUE
170 TRUE 5 TextUnit_Character 1 lastCharPos lastCharPos 1 docEnd docEnd TRUE
171 TRUE 5 TextUnit_Character 5 lastCharPos lastCharPos 1 docEnd docEnd TRUE
172 FALSE 5 TextUnit_Character -5 lastCharPos lastCharPosP1C -5 lastCharPosM5C lastCharPosM4C TRUE
173 FALSE 5 TextUnit_Character -1 lastCharPos lastCharPosP1C -1 lastCharPosM1C lastCharPos TRUE
174 FALSE 5 TextUnit_Character 0 lastCharPos lastCharPosP1C 0 lastCharPos docEnd TRUE
175 FALSE 5 TextUnit_Character 1 lastCharPos lastCharPosP1C 0 lastCharPos docEnd TRUE
176 FALSE 5 TextUnit_Character 5 lastCharPos lastCharPosP1C 0 lastCharPos docEnd TRUE
177 TRUE 5 TextUnit_Line -5 lastCharPos lastCharPos -5 lastCharPosM4L lastCharPosM4L TRUE
178 TRUE 5 TextUnit_Line -1 lastCharPos lastCharPos -1 lastCharPosLeft lastCharPosLeft TRUE
179 TRUE 5 TextUnit_Line 0 lastCharPos lastCharPos 0 lastCharPos lastCharPos TRUE
180 TRUE 5 TextUnit_Line 1 lastCharPos lastCharPos 1 docEnd docEnd TRUE
181 TRUE 5 TextUnit_Line 5 lastCharPos lastCharPos 1 docEnd docEnd TRUE
182 FALSE 5 TextUnit_Line -5 lastCharPos lastCharPosP1C -5 lastCharPosM5L lastCharPosM4L TRUE
183 FALSE 5 TextUnit_Line -1 lastCharPos lastCharPosP1C -1 lastCharPosM1L lastCharPosLeft TRUE
184 FALSE 5 TextUnit_Line 0 lastCharPos lastCharPosP1C 0 lastCharPosLeft docEnd TRUE
185 FALSE 5 TextUnit_Line 1 lastCharPos lastCharPosP1C 0 lastCharPosLeft docEnd TRUE
186 FALSE 5 TextUnit_Line 5 lastCharPos lastCharPosP1C 0 lastCharPosLeft docEnd TRUE
187 TRUE 5 TextUnit_Document -5 lastCharPos lastCharPos -1 origin origin TRUE
188 TRUE 5 TextUnit_Document -1 lastCharPos lastCharPos -1 origin origin TRUE
189 TRUE 5 TextUnit_Document 0 lastCharPos lastCharPos 0 lastCharPos lastCharPos TRUE
190 TRUE 5 TextUnit_Document 1 lastCharPos lastCharPos 1 docEnd docEnd TRUE
191 TRUE 5 TextUnit_Document 5 lastCharPos lastCharPos 1 docEnd docEnd TRUE
192 FALSE 5 TextUnit_Document -5 lastCharPos lastCharPosP1C 0 origin docEnd TRUE
193 FALSE 5 TextUnit_Document -1 lastCharPos lastCharPosP1C 0 origin docEnd TRUE
194 FALSE 5 TextUnit_Document 0 lastCharPos lastCharPosP1C 0 origin docEnd TRUE
195 FALSE 5 TextUnit_Document 1 lastCharPos lastCharPosP1C 0 origin docEnd TRUE
196 FALSE 5 TextUnit_Document 5 lastCharPos lastCharPosP1C 0 origin docEnd TRUE
197 TRUE 6 TextUnit_Character -5 docEnd docEnd -5 docEndM5C docEndM5C TRUE
198 TRUE 6 TextUnit_Character -1 docEnd docEnd -1 docEndM1C docEndM1C TRUE
199 TRUE 6 TextUnit_Character 0 docEnd docEnd 0 docEnd docEnd TRUE
200 TRUE 6 TextUnit_Character 1 docEnd docEnd 0 docEnd docEnd TRUE
201 TRUE 6 TextUnit_Character 5 docEnd docEnd 0 docEnd docEnd TRUE
202 FALSE 6 TextUnit_Character -5 docEnd docEndP1C -5 docEndM5C docEndM5C TRUE
203 FALSE 6 TextUnit_Character -1 docEnd docEndP1C -1 docEndM1C docEndM1C TRUE
204 FALSE 6 TextUnit_Character 0 docEnd docEndP1C 0 docEnd docEnd TRUE
205 FALSE 6 TextUnit_Character 1 docEnd docEndP1C 0 docEnd docEnd TRUE
206 FALSE 6 TextUnit_Character 5 docEnd docEndP1C 0 docEnd docEnd TRUE
207 TRUE 6 TextUnit_Line -5 docEnd docEnd -5 docEndM4L docEndM4L TRUE
208 TRUE 6 TextUnit_Line -1 docEnd docEnd -1 docEndLeft docEndLeft TRUE
209 TRUE 6 TextUnit_Line 0 docEnd docEnd 0 docEnd docEnd TRUE
210 TRUE 6 TextUnit_Line 1 docEnd docEnd 0 docEnd docEnd TRUE
211 TRUE 6 TextUnit_Line 5 docEnd docEnd 0 docEnd docEnd TRUE
212 FALSE 6 TextUnit_Line -5 docEnd docEndP1C -5 docEndM4L docEndM4L TRUE
213 FALSE 6 TextUnit_Line -1 docEnd docEndP1C -1 docEndLeft docEndLeft TRUE
214 FALSE 6 TextUnit_Line 0 docEnd docEndP1C 0 docEnd docEnd TRUE
215 FALSE 6 TextUnit_Line 1 docEnd docEndP1C 0 docEnd docEnd TRUE
216 FALSE 6 TextUnit_Line 5 docEnd docEndP1C 0 docEnd docEnd TRUE
217 TRUE 6 TextUnit_Document -5 docEnd docEnd -1 origin origin TRUE
218 TRUE 6 TextUnit_Document -1 docEnd docEnd -1 origin origin TRUE
219 TRUE 6 TextUnit_Document 0 docEnd docEnd 0 docEnd docEnd TRUE
220 TRUE 6 TextUnit_Document 1 docEnd docEnd 0 docEnd docEnd TRUE
221 TRUE 6 TextUnit_Document 5 docEnd docEnd 0 docEnd docEnd TRUE
222 FALSE 6 TextUnit_Document -5 docEnd docEndP1C -1 origin origin TRUE
223 FALSE 6 TextUnit_Document -1 docEnd docEndP1C -1 origin origin TRUE
224 FALSE 6 TextUnit_Document 0 docEnd docEndP1C 0 docEnd docEnd TRUE
225 FALSE 6 TextUnit_Document 1 docEnd docEndP1C 0 docEnd docEnd TRUE
226 FALSE 6 TextUnit_Document 5 docEnd docEndP1C 0 docEnd docEnd TRUE
227 TRUE 7 TextUnit_Character -5 midEmptySpace midEmptySpace -5 docEndM5C docEndM5C TRUE
228 TRUE 7 TextUnit_Character -1 midEmptySpace midEmptySpace -1 docEndM1C docEndM1C TRUE
229 TRUE 7 TextUnit_Character 0 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
230 TRUE 7 TextUnit_Character 1 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
231 TRUE 7 TextUnit_Character 5 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
232 FALSE 7 TextUnit_Character -5 midEmptySpace midEmptySpaceP1C -5 docEndM5C docEndM5C TRUE
233 FALSE 7 TextUnit_Character -1 midEmptySpace midEmptySpaceP1C -1 docEndM1C docEndM1C TRUE
234 FALSE 7 TextUnit_Character 0 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE
235 FALSE 7 TextUnit_Character 1 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE
236 FALSE 7 TextUnit_Character 5 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE
237 TRUE 7 TextUnit_Line -5 midEmptySpace midEmptySpace -5 docEndM4L docEndM4L TRUE
238 TRUE 7 TextUnit_Line -1 midEmptySpace midEmptySpace -1 docEndLeft docEndLeft TRUE
239 TRUE 7 TextUnit_Line 0 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
240 TRUE 7 TextUnit_Line 1 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
241 TRUE 7 TextUnit_Line 5 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
242 FALSE 7 TextUnit_Line -5 midEmptySpace midEmptySpaceP1C -5 docEndM4L docEndM4L TRUE
243 FALSE 7 TextUnit_Line -1 midEmptySpace midEmptySpaceP1C -1 docEndLeft docEndLeft TRUE
244 FALSE 7 TextUnit_Line 0 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE
245 FALSE 7 TextUnit_Line 1 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE
246 FALSE 7 TextUnit_Line 5 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE
247 TRUE 7 TextUnit_Document -5 midEmptySpace midEmptySpace -1 origin origin TRUE
248 TRUE 7 TextUnit_Document -1 midEmptySpace midEmptySpace -1 origin origin TRUE
249 TRUE 7 TextUnit_Document 0 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
250 TRUE 7 TextUnit_Document 1 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
251 TRUE 7 TextUnit_Document 5 midEmptySpace midEmptySpace 0 docEnd docEnd TRUE
252 FALSE 7 TextUnit_Document -5 midEmptySpace midEmptySpaceP1C -1 origin origin TRUE
253 FALSE 7 TextUnit_Document -1 midEmptySpace midEmptySpaceP1C -1 origin origin TRUE
254 FALSE 7 TextUnit_Document 0 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE
255 FALSE 7 TextUnit_Document 1 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE
256 FALSE 7 TextUnit_Document 5 midEmptySpace midEmptySpaceP1C 0 docEnd docEnd TRUE