diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index 5f2703f73666..9ed2042bdd97 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -1,8 +1,11 @@ +AAAAA AAAAAABBBBBBCCC AAAAABBBBBBBCCC +AAAAABBBBBBCCC AAAAABCCCCCCCCC AAAAADCCCCCCCCC ABANDONFONT +abcd ABCDEFGHIJKLMNO ABCG abf @@ -129,6 +132,7 @@ backstory Batang baz Bazz +BBBBBCCC BBBBCCCCC BBDM bbwe @@ -290,6 +294,7 @@ CNTRL codebase Codeflow codepage +codepath codepoint codeproject COINIT @@ -755,6 +760,7 @@ FFrom FGCOLOR fgetc fgetwc +FGHIJ fgidx FILEDESCRIPTION fileno @@ -892,6 +898,9 @@ GFEh Gfun gfx gh +GHIJK +GHIJKL +GHIJKLM gitfilters github gitlab @@ -1198,6 +1207,7 @@ KILLFOCUS kinda KJ KLF +KLMNO KLMNOPQRST KLMNOPQRSTQQQQQ KU @@ -1244,6 +1254,7 @@ lld llvm llx LMENU +LMNOP lnk lnkd lnkfile @@ -1286,6 +1297,7 @@ LPFNADDPROPSHEETPAGE LPINT lpl LPMEASUREITEMSTRUCT +LPMINMAXINFO lpmsg LPNEWCPLINFO LPNEWCPLINFOA @@ -1317,6 +1329,7 @@ lsproj lss lstatus lstrcmp +lstrcmpi LTEXT LTLTLTLTL ltype @@ -1383,6 +1396,7 @@ mindbogglingly mingw minimizeall minkernel +MINMAXINFO minwin minwindef Mip @@ -1392,6 +1406,8 @@ mmcc MMCPL mmsystem MNC +MNOPQ +MNOPQR MODALFRAME modelproj MODERNCORE @@ -1487,9 +1503,9 @@ Nls NLSMODE NOACTIVATE NOAPPLYNOW -NOCOMM NOCLIP NOCOLOR +NOCOMM NOCONTEXTHELP NOCOPYBITS nodiscard @@ -1513,6 +1529,7 @@ NONPREROTATED nonspace NOOWNERZORDER NOPAINT +NOPQRST noprofile NOREDRAW NOREMOVE @@ -1866,6 +1883,7 @@ pythonw qi QJ qo +QRSTU qsort queryable QUESTIONMARK @@ -2213,6 +2231,7 @@ strrev strsafe strtok structs +STUVWX STX stylecop SUA @@ -2506,6 +2525,8 @@ utr uuid uuidof uuidv +UVWX +UVWXY UWA uwp uxtheme @@ -2573,6 +2594,7 @@ VTRGBTo vtseq vtterm vttest +VWX waaay waitable waivable @@ -2812,6 +2834,8 @@ YVIRTUALSCREEN Yw YWalk yx +YZ +yzx Zc ZCmd ZCtrl @@ -2822,9 +2846,3 @@ zsh zu zxcvbnm zy -AAAAABBBBBBCCC -BBBBBCCC -abcd -LPMINMAXINFO -MINMAXINFO -lstrcmpi diff --git a/src/buffer/out/CharRow.hpp b/src/buffer/out/CharRow.hpp index b2f7ab41c1cf..ce797b0f17c7 100644 --- a/src/buffer/out/CharRow.hpp +++ b/src/buffer/out/CharRow.hpp @@ -81,9 +81,11 @@ class CharRow final // iterators iterator begin() noexcept; const_iterator cbegin() const noexcept; + const_iterator begin() const noexcept { return cbegin(); } iterator end() noexcept; const_iterator cend() const noexcept; + const_iterator end() const noexcept { return cend(); } UnicodeStorage& GetUnicodeStorage() noexcept; const UnicodeStorage& GetUnicodeStorage() const noexcept; diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp new file mode 100644 index 000000000000..2f69b6c6631c --- /dev/null +++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp @@ -0,0 +1,856 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "WexTestClass.h" +#include "../../inc/consoletaeftemplates.hpp" + +#include "../textBuffer.hpp" +#include "../../renderer/inc/DummyRenderTarget.hpp" +#include "../../types/inc/Utf16Parser.hpp" +#include "../../types/inc/GlyphWidth.hpp" + +#include + +template<> +class WEX::TestExecution::VerifyOutputTraits +{ +public: + static WEX::Common::NoThrowString ToString(const wchar_t& wch) + { + return WEX::Common::NoThrowString().Format(L"'%c'", wch); + } +}; + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +namespace +{ + struct TestRow + { + std::wstring_view text; + bool wrap; + }; + + struct TestBuffer + { + COORD size; + std::vector rows; + COORD cursor; + }; + + struct TestCase + { + std::wstring_view name; + std::vector buffers; + }; + + static constexpr auto true_due_to_exact_wrap_bug{ true }; + + static const TestCase testCases[] = { + TestCase{ + L"No reflow required", + { + TestBuffer{ + { 6, 5 }, + { + { L"AB ", false }, + { L"$ ", false }, + { L"CD ", false }, + { L"EFG ", false }, + { L" ", false }, + }, + { 0, 1 } // cursor on $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + { L"AB ", false }, + { L"$ ", false }, + { L"CD ", false }, + { L"EFG ", false }, + { L" ", false }, + }, + { 0, 1 } // cursor on $ + }, + TestBuffer{ + { 4, 5 }, + { + { L"AB ", false }, + { L"$ ", false }, + { L"CD ", false }, + { L"EFG ", false }, + { L" ", false }, + }, + { 0, 1 } // cursor on $ + }, + }, + }, + TestCase{ + L"SBCS, cursor remains in buffer, no circling, no original wrap", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", false }, + { L"$ ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 0, 1 } // cursor on $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + { L"ABCDE", true }, + { L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line. + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 1, 1 } // cursor on $ + }, + TestBuffer{ + { 6, 5 }, // grow width back to original + { + { L"ABCDEF", true_due_to_exact_wrap_bug }, + { L"$ ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 0, 1 } // cursor on $ + }, + TestBuffer{ + { 7, 5 }, // grow width wider than original + { + { L"ABCDEF$", true_due_to_exact_wrap_bug }, // EXACT WRAP BUG: $ should be alone on next line + { L" ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 6, 0 } // cursor on $ + }, + }, + }, + TestCase{ + L"SBCS, cursor remains in buffer, no circling, with original wrap", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", true }, + { L"G$ ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 1, 1 } // cursor on $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + { L"ABCDE", true }, + { L"FG$ ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 2, 1 } // cursor on $ + }, + TestBuffer{ + { 6, 5 }, // grow width back to original + { + { L"ABCDEF", true }, + { L"G$ ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 1, 1 } // cursor on $ + }, + TestBuffer{ + { 7, 5 }, // grow width wider than original + { + { L"ABCDEFG", true }, + { L"$ ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 0, 1 } // cursor on $ + }, + }, + }, + TestCase{ + L"SBCS line padded with spaces (to wrap)", + { + TestBuffer{ + { 6, 5 }, + { + { L"AB ", true }, // AB $ CD is one long wrapped line + { L"$ ", true }, + { L"CD ", false }, + { L"EFG ", false }, + { L" ", false }, + }, + { 0, 1 } // cursor on $ + }, + TestBuffer{ + { 7, 5 }, // reduce width by 1 + { + { L"AB $", true }, + { L" CD", true_due_to_exact_wrap_bug }, + { L" ", false }, + { L"EFG ", false }, + { L" ", false }, + }, + { 6, 0 } // cursor on $ + }, + TestBuffer{ + { 8, 5 }, + { + { L"AB $ ", true }, + { L" CD ", false }, // Goes to false because we hit the end of ..CD + { L"EFG ", false }, // [BUG] EFG moves up due to exact wrap bug above + { L" ", false }, + { L" ", false }, + }, + { 6, 0 } // cursor on $ + }, + }, + }, + TestCase{ + L"DBCS, cursor remains in buffer, no circling, with original wrap", + { + TestBuffer{ + { 6, 5 }, + { + //--0123456-- + { L"カタカ", true }, // KA TA KA + { L"ナ$ ", false }, // NA + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 2, 1 } // cursor on $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + //--012345-- + { L"カタ ", true }, // KA TA [FORCED SPACER] + { L"カナ$", true_due_to_exact_wrap_bug }, // KA NA + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 4, 1 } // cursor on $ + }, + TestBuffer{ + { 6, 5 }, // grow width back to original + { + //--0123456-- + { L"カタカ", true }, // KA TA KA + { L"ナ$ ", false }, // NA + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 2, 1 } // cursor on $ + }, + TestBuffer{ + { 7, 5 }, // grow width wider than original (by one; no visible change!) + { + //--0123456-- + { L"カタカ ", true }, // KA TA KA [FORCED SPACER] + { L"ナ$ ", false }, // NA + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 2, 1 } // cursor on $ + }, + TestBuffer{ + { 8, 5 }, // grow width enough to fit second DBCS + { + //--01234567-- + { L"カタカナ", true }, // KA TA KA NA + { L"$ ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 0, 1 } // cursor on $ + }, + }, + }, + TestCase{ + L"SBCS, cursor remains in buffer, with circling, no original wrap", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", false }, + { L"$ ", false }, + { L"GHIJKL", false }, + { L"MNOPQR", false }, + { L"STUVWX", false }, + }, + { 0, 1 } // cursor on $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + { L"F$ ", false }, + { L"GHIJK", true }, // [BUG] We should see GHIJK\n L\n MNOPQ\n R\n + { L"LMNOP", true }, // The wrapping here is irregular + { L"QRSTU", true }, + { L"VWX ", false }, + }, + { 1, 1 } // [BUG] cursor moves to 1,1 instead of sticking with the $ + }, + TestBuffer{ + { 6, 5 }, // going back to 6,5, the data lost has been destroyed + { + //{ L"F$ ", false }, // [BUG] this line is erroneously destroyed too! + { L"GHIJKL", true }, + { L"MNOPQR", true }, + { L"STUVWX", true }, + { L" ", false }, + { L" ", false }, // [BUG] this line is added + }, + { 1, 1 }, // [BUG] cursor does not follow [H], it sticks at 1,1 + }, + TestBuffer{ + { 7, 5 }, // a number of errors are carried forward from the previous buffer + { + { L"GHIJKLM", true }, + { L"NOPQRST", true }, + { L"UVWX ", false }, // [BUG] This line loses wrap for some reason + { L" ", false }, + { L" ", false }, + }, + { 0, 1 }, // [BUG] The cursor moves to 0, 1 now, sticking with the [N] from before + }, + }, + }, + TestCase{ + // The cursor is not found during character insertion. + // Instead, it is found off the right edge of the text. This triggers + // a separate cursor found codepath in the original algorithm. + L"SBCS, cursor off rightmost char in non-wrapped line", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", false }, + { L"$ ", false }, + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 1, 1 } // cursor *after* $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + { L"ABCDE", true }, + { L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line. + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 2, 1 } // cursor follows space after $ to next line + }, + }, + }, + TestCase{ + L"SBCS, cursor off rightmost char in wrapped line, which is then pushed off bottom", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", true }, + { L"GHIJKL", true }, + { L"MNOPQR", true }, + { L"STUVWX", true }, + { L"YZ0 $ ", false }, + }, + { 5, 4 } // cursor *after* $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + { L"FGHIJ", true }, + { L"KLMNO", true }, + { L"PQRST", true }, + { L"UVWXY", true }, + { L"Z0 $ ", false }, + }, + { 4, 4 } // cursor follows space after $ to newly introduced bottom line + }, + }, + }, + TestCase{ + L"SBCS, cursor off in space to far right of text (end of buffer content)", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", false }, + // v cursor + { L"$ ", false }, + // ^ cursor + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 5, 1 } // cursor in space far after $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + { L"ABCDE", true }, + { L"F$ ", true }, // [BUG] This line is marked wrapped, and I do not know why + // v cursor + { L" ", false }, + // ^ cursor + { L" ", false }, + { L" ", false }, + }, + { 1, 2 } // cursor stays same linear distance from $ + }, + TestBuffer{ + { 6, 5 }, // grow back to original size + { + { L"ABCDEF", true_due_to_exact_wrap_bug }, + // v cursor [BUG] cursor does not retain linear distance from $ + { L"$ ", false }, + // ^ cursor + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 4, 1 } // cursor stays same linear distance from $ + }, + }, + }, + TestCase{ + L"SBCS, cursor off in space to far right of text (middle of buffer content)", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", false }, + // v cursor + { L"$ ", false }, + // ^ cursor + { L"BLAH ", false }, + { L"BLAH ", false }, + { L" ", false }, + }, + { 5, 1 } // cursor in space far after $ + }, + TestBuffer{ + { 5, 5 }, // reduce width by 1 + { + { L"ABCDE", true }, + { L"F$ ", false }, + { L"BLAH ", false }, + { L"BLAH ", true }, // [BUG] this line wraps, no idea why + // v cursor [BUG] cursor erroneously moved to end of all content + { L" ", false }, + // ^ cursor + }, + { 0, 4 } }, + TestBuffer{ + { 6, 5 }, // grow back to original size + { + { L"ABCDEF", true }, + { L"$ ", false }, + { L"BLAH ", false }, + // v cursor [BUG] cursor is pulled up to previous line because it was marked wrapped + { L"BLAH ", false }, + // ^ cursor + { L" ", false }, + }, + { 5, 3 } }, + }, + }, + TestCase{ + // Shrinking the buffer this much forces a multi-line wrap before the cursor + L"SBCS, cursor off in space to far right of text (end of buffer content), aggressive shrink", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", false }, + // v cursor + { L"$ ", false }, + // ^ cursor + { L" ", false }, + { L" ", false }, + { L" ", false }, + }, + { 5, 1 } // cursor in space far after $ + }, + TestBuffer{ + { 2, 5 }, // reduce width aggressively + { + { L"CD", true }, + { L"EF", true }, + { L"$ ", true }, + { L" ", true }, + // v cursor + { L" ", false }, + // ^ cursor + }, + { 1, 4 } }, + }, + }, + TestCase{ + L"SBCS, cursor off in space to far right of text (end of buffer content), fully wrapped, aggressive shrink", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", true }, + // v cursor + { L"$ ", true }, + // ^ cursor + { L" ", true }, + { L" ", true }, + { L" ", true }, + }, + { 5, 1 } // cursor in space far after $ + }, + TestBuffer{ + { 2, 5 }, // reduce width aggressively + { + { L"EF", true }, + { L"$ ", true }, + { L" ", true }, + { L" ", true }, + // v cursor [BUG] cursor does not maintain linear distance from $ + { L" ", false }, + // ^ cursor + }, + { 1, 4 } }, + }, + }, + TestCase{ + L"SBCS, cursor off in space to far right of text (middle of buffer content), fully wrapped, aggressive shrink", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", true }, + // v cursor + { L"$ ", true }, + // ^ cursor + { L" ", true }, + { L" ", true }, + { L" Q", true }, + }, + { 5, 1 } // cursor in space far after $ + }, + TestBuffer{ + { 2, 5 }, // reduce width aggressively + { + { L" ", true }, + { L" ", true }, + { L" ", true }, + { L" Q", true }, + // v cursor [BUG] cursor jumps to end of world + { L" ", false }, // POTENTIAL [BUG] a whole new blank line is added for the cursor + // ^ cursor + }, + { 1, 4 } }, + }, + }, + TestCase{ + L"SBCS, cursor off in space to far right of text (middle of buffer content), partially wrapped, aggressive shrink", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", false }, + // v cursor + { L"$ ", false }, + // ^ cursor + { L" ", false }, + { L" ", true }, + { L" Q", true }, + }, + { 5, 1 } // cursor in space far after $ + }, + TestBuffer{ + { 2, 5 }, // reduce width aggressively + { + { L" ", true }, + { L" ", true }, + { L" ", true }, + { L" Q", true }, + // v cursor [BUG] cursor jumps to different place than fully wrapped case + { L" ", false }, + // ^ cursor + }, + { 0, 4 } }, + }, + }, + TestCase{ + // This triggers the cursor being walked forward w/ newlines to maintain + // distance from the last char in the buffer + L"SBCS, cursor at end of buffer, otherwise same as previous test", + { + TestBuffer{ + { 6, 5 }, + { + { L"ABCDEF", false }, + { L"$ ", false }, + { L" Q", true }, + { L" ", true }, + // v cursor + { L" ", true }, + // ^ cursor + }, + { 5, 4 } // cursor at end of buffer + }, + TestBuffer{ + { 2, 5 }, // reduce width aggressively + { + { L" ", true }, + { L" ", true }, + { L" Q", true }, + { L" ", false }, + // v cursor [BUG] cursor loses linear distance from Q; is this important? + { L" ", false }, + // ^ cursor + }, + { 0, 4 } }, + }, + }, + }; + +#pragma region TAEF hookup for the test case array above + struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass, 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(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 = nullptr; + return S_FALSE; + } + + private: + size_t _index; + }; + + struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass, IDataSource> + { + STDMETHODIMP Advance(IDataRow** ppDataRow) override + { + if (_index < std::extent::value) + { + Microsoft::WRL::MakeAndInitialize(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 ReflowTestDataSource(IDataSource** ppDataSource, void*) +{ + auto source{ Microsoft::WRL::Make() }; + return source.CopyTo(ppDataSource); +} + +class ReflowTests +{ + TEST_CLASS(ReflowTests); + + static DummyRenderTarget target; + static std::unique_ptr _textBufferFromTestBuffer(const TestBuffer& testBuffer) + { + auto buffer = std::make_unique(testBuffer.size, TextAttribute{ 0x7 }, 0, target); + + size_t i{}; + for (const auto& testRow : testBuffer.rows) + { + auto& row{ buffer->GetRowByOffset(i) }; + + auto& charRow{ row.GetCharRow() }; + charRow.SetWrapForced(testRow.wrap); + + size_t j{}; + for (auto it{ charRow.begin() }; it != charRow.end(); ++it) + { + // Yes, we're about to manually create a buffer. It is unpleasant. + const auto ch{ til::at(testRow.text, j) }; + it->Char() = ch; + if (IsGlyphFullWidth(ch)) + { + it->DbcsAttr().SetLeading(); + it++; + it->Char() = ch; + it->DbcsAttr().SetTrailing(); + } + else + { + it->DbcsAttr().SetSingle(); + } + j++; + } + i++; + } + + buffer->GetCursor().SetPosition(testBuffer.cursor); + return buffer; + } + + static std::unique_ptr _textBufferByReflowingTextBuffer(TextBuffer& originalBuffer, const COORD newSize) + { + auto buffer = std::make_unique(newSize, TextAttribute{ 0x7 }, 0, target); + TextBuffer::Reflow(originalBuffer, *buffer, std::nullopt, std::nullopt); + return buffer; + } + + static void _compareTextBufferAgainstTestBuffer(const TextBuffer& buffer, const TestBuffer& testBuffer) + { + VERIFY_ARE_EQUAL(testBuffer.cursor, buffer.GetCursor().GetPosition()); + VERIFY_ARE_EQUAL(testBuffer.size, buffer.GetSize().Dimensions()); + + size_t i{}; + for (const auto& testRow : testBuffer.rows) + { + NoThrowString indexString; + const auto& row{ buffer.GetRowByOffset(i) }; + + const auto& charRow{ row.GetCharRow() }; + + indexString.Format(L"[Row %d]", i); + VERIFY_ARE_EQUAL(testRow.wrap, charRow.WasWrapForced(), indexString); + + size_t j{}; + for (auto it{ charRow.begin() }; it != charRow.end(); ++it) + { + indexString.Format(L"[Cell %d, %d; Text line index %d]", it - charRow.begin(), i, j); + // Yes, we're about to manually create a buffer. It is unpleasant. + const auto ch{ til::at(testRow.text, j) }; + if (IsGlyphFullWidth(ch)) + { + // Char is full width in test buffer, so + // ensure that real buffer is LEAD, TRAIL (ch) + VERIFY_IS_TRUE(it->DbcsAttr().IsLeading(), indexString); + VERIFY_ARE_EQUAL(ch, it->Char(), indexString); + + it++; + VERIFY_IS_TRUE(it->DbcsAttr().IsTrailing(), indexString); + } + else + { + VERIFY_IS_TRUE(it->DbcsAttr().IsSingle(), indexString); + } + + VERIFY_ARE_EQUAL(ch, it->Char(), indexString); + j++; + } + i++; + } + } + + TEST_METHOD(TestReflowCases) + { + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"DataSource", L"Export:ReflowTestDataSource") + 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{ testCases[i] }; + Log::Comment(NoThrowString().Format(L"[%zu.0] Test case \"%.*s\"", i, testCase.name.size(), testCase.name.data())); + + // Create initial text buffer from Buffer 0 + auto textBuffer{ _textBufferFromTestBuffer(testCase.buffers.front()) }; + for (size_t bufferIndex{ 1 }; bufferIndex < testCase.buffers.size(); ++bufferIndex) + { + const auto& testBuffer{ til::at(testCase.buffers, bufferIndex) }; + Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.X, testBuffer.size.Y)); + + auto newBuffer{ _textBufferByReflowingTextBuffer(*textBuffer, testBuffer.size) }; + + // All future operations are based on the new buffer + std::swap(textBuffer, newBuffer); + + _compareTextBufferAgainstTestBuffer(*textBuffer, testBuffer); + } + } +}; + +DummyRenderTarget ReflowTests::target{}; diff --git a/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj b/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj index 0b2492922f36..b8821689c279 100644 --- a/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj +++ b/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj @@ -10,6 +10,7 @@ + @@ -18,6 +19,9 @@ + + {18d09a24-8240-42d6-8cb6-236eee820263} + {0cf235bd-2da0-407e-90ee-c467e8bbc714} diff --git a/src/buffer/out/ut_textbuffer/sources b/src/buffer/out/ut_textbuffer/sources index ba50be8e342e..53fd1ee63414 100644 --- a/src/buffer/out/ut_textbuffer/sources +++ b/src/buffer/out/ut_textbuffer/sources @@ -14,6 +14,7 @@ DLLDEF = SOURCES = \ $(SOURCES) \ + ReflowTests.cpp \ TextColorTests.cpp \ TextAttributeTests.cpp \ DefaultResource.rc \