terminal/src/terminal/adapter/ut_adapter/adapterTest.cpp
James Holderness b604117421
Standardize the color table order (#11602)
## Summary of the Pull Request

In the original implementation, we used two different orderings for the color tables. The WT color table used ANSI order, while the conhost color table used a Windows-specific order. This PR standardizes on the ANSI color order everywhere, so the usage of indexed colors is consistent across both parts of the code base, which will hopefully allow more of the code to be shared one day.

## References

This is another small step towards de-duplicating `AdaptDispatch` and `TerminalDispatch` for issue #3849, and is essentially a followup to the SGR dispatch refactoring in PR #6728.

## PR Checklist
* [x] Closes #11461
* [x] CLA signed.
* [x] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. Issue number where discussion took place: #11461

## Detailed Description of the Pull Request / Additional comments

Conhost still needs to deal with legacy attributes using Windows color order, so those values now need to be transposed to ANSI colors order when creating a `TextAttribute` object. This is done with a simple mapping table, which also handles the translation of the default color entries, so it's actually slightly faster than the original code.

And when converting `TextAttribute` values back to legacy console attributes, we were already using a mapping table to handle the narrowing of 256-color values down to 16 colors, so we just needed to adjust that table to account for the translation from ANSI to Windows, and then could make use of the same table for both 256-color and 16-color values.

There are also a few places in conhost that read from or write to the color tables, and those now need to transpose the index values. I've addressed this by creating separate `SetLegacyColorTableEntry` and `GetLegacyColorTableEntry` methods in the `Settings` class which take care of the mapping, so it's now clearer in which cases the code is dealing with legacy values, and which are ANSI values.

These methods are used in the `SetConsoleScreenBufferInfoEx` and `GetConsoleScreenBufferInfoEx` APIs, as well as a few place where color preferences are handled (the registry, shortcut links, and the properties dialog), none of which are particularly sensitive to performance. However, we also use the legacy table when looking up the default colors for rendering (which happens a lot), so I've refactored that code so the default color calculations now only occur once per frame.

The plus side of all of this is that the VT code doesn't need to do the index translation anymore, so we can finally get rid of all the calls to `XTermToWindowsIndex`, and we no longer need a separate color table initialization method for conhost, so I was able to merge a number of color initialization methods into one. We also no longer need to translate from legacy values to ANSI when generating VT sequences for conpty.

The one exception to that is the 16-color VT renderer, which uses the `TextColor::GetLegacyIndex` method to approximate 16-color equivalents for RGB and 256-color values. Since that method returns a legacy index, it still needs to be translated to ANSI before it can be used in a VT sequence. But this should be no worse than it was before.

One more special case is conhost's secret _Color Selection_ feature. That uses `Ctrl`+Number and `Alt`+Number key sequences to highlight parts of the buffer, and the mapping from number to color is based on the Windows color order. So that mapping now needs to be transposed, but that's also not performance sensitive.

The only thing that I haven't bothered to update is the trace logging code in the `Telemetry` class, which logs the first 16 entries in the color table. Those entries are now going to be in a different order, but I didn't think that would be of great concern to anyone.

## Validation Steps Performed

A lot of unit tests needed to be updated to use ANSI color constants when setting indexed colors, where before they might have been expecting values in Windows order. But this replaced a wild mix of different constants, sometimes having to use bit shifting, as well as values mapped with `XTermToWindowsIndex`, so I think the tests are a whole lot clearer now. Only a few cases have been left with literal numbers where that seemed more appropriate.

In addition to getting the unit tests working, I've also manually tested the behaviour of all the console APIs which I thought could be affected by these changes, and confirmed that they produced the same results in the new code as they did in the original implementation.

This includes:
- `WriteConsoleOutput`
- `ReadConsoleOutput`
- `SetConsoleTextAttribute` with `WriteConsoleOutputCharacter`
- `FillConsoleOutputAttribute` and `FillConsoleOutputCharacter` 
- `ScrollConsoleScreenBuffer`
- `GetConsoleScreenBufferInfo`
- `GetConsoleScreenBufferInfoEx`
- `SetConsoleScreenBufferInfoEx`

I've also manually tested changing colors via the console properties menu, the registry, and shortcut links, including setting default colors and popup colors. And I've tested that the "Quirks Mode" is still working as expected in PowerShell.

In terms of performance, I wrote a little test app that filled a 80x9999 buffer with random color combinations using `WriteConsoleOutput`, which I figured was likely to be the most performance sensitive call, and I think it now actually performs slightly better than the original implementation.

I've also tested similar code - just filling the visible window - with SGR VT sequences of various types, and the performance seems about the same as it was before.
2021-11-04 22:13:22 +00:00

2630 lines
118 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <wextestclass.h>
#include "../../inc/consoletaeftemplates.hpp"
#include "adaptDispatch.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
namespace Microsoft
{
namespace Console
{
namespace VirtualTerminal
{
class AdapterTest;
class ConAdapterTestGetSet;
};
};
};
enum class CursorY
{
TOP,
BOTTOM,
YCENTER
};
enum class CursorX
{
LEFT,
RIGHT,
XCENTER
};
enum class CursorDirection : size_t
{
UP = 0,
DOWN = 1,
RIGHT = 2,
LEFT = 3,
NEXTLINE = 4,
PREVLINE = 5
};
enum class AbsolutePosition : size_t
{
CursorHorizontal = 0,
VerticalLine = 1,
};
using namespace Microsoft::Console::VirtualTerminal;
class TestGetSet final : public ConGetSet
{
public:
bool GetConsoleScreenBufferInfoEx(CONSOLE_SCREEN_BUFFER_INFOEX& sbiex) const override
{
Log::Comment(L"GetConsoleScreenBufferInfoEx MOCK returning data...");
if (_getConsoleScreenBufferInfoExResult)
{
sbiex.dwSize = _bufferSize;
sbiex.srWindow = _viewport;
sbiex.dwCursorPosition = _cursorPos;
sbiex.wAttributes = _attribute.GetLegacyAttributes();
}
return _getConsoleScreenBufferInfoExResult;
}
bool SetConsoleScreenBufferInfoEx(const CONSOLE_SCREEN_BUFFER_INFOEX& sbiex) override
{
Log::Comment(L"SetConsoleScreenBufferInfoEx MOCK returning data...");
if (_setConsoleScreenBufferInfoExResult)
{
VERIFY_ARE_EQUAL(_expectedCursorPos, sbiex.dwCursorPosition);
VERIFY_ARE_EQUAL(_expectedScreenBufferSize, sbiex.dwSize);
VERIFY_ARE_EQUAL(_expectedScreenBufferViewport, sbiex.srWindow);
VERIFY_ARE_EQUAL(_expectedAttribute, TextAttribute{ sbiex.wAttributes });
}
return _setConsoleScreenBufferInfoExResult;
}
bool SetConsoleCursorPosition(const COORD position) override
{
Log::Comment(L"SetConsoleCursorPosition MOCK called...");
if (_setConsoleCursorPositionResult)
{
VERIFY_ARE_EQUAL(_expectedCursorPos, position);
_cursorPos = position;
}
return _setConsoleCursorPositionResult;
}
bool SetConsoleWindowInfo(const bool absolute, const SMALL_RECT& window) override
{
Log::Comment(L"SetConsoleWindowInfo MOCK called...");
if (_setConsoleWindowInfoResult)
{
VERIFY_ARE_EQUAL(_expectedWindowAbsolute, absolute);
VERIFY_ARE_EQUAL(_expectedConsoleWindow, window);
_viewport = window;
}
return _setConsoleWindowInfoResult;
}
bool SetInputMode(const TerminalInput::Mode mode, const bool enabled) override
{
Log::Comment(L"SetInputMode MOCK called...");
if (_setInputModeResult)
{
VERIFY_ARE_EQUAL(_expectedInputMode, mode);
VERIFY_ARE_EQUAL(_expectedInputModeEnabled, enabled);
}
return _setInputModeResult;
}
bool PrivateSetAnsiMode(const bool ansiMode) override
{
Log::Comment(L"PrivateSetAnsiMode MOCK called...");
if (_privateSetAnsiModeResult)
{
VERIFY_ARE_EQUAL(_expectedAnsiMode, ansiMode);
}
return _privateSetAnsiModeResult;
}
bool PrivateSetScreenMode(const bool /*reverseMode*/) override
{
Log::Comment(L"PrivateSetScreenMode MOCK called...");
return true;
}
bool PrivateSetAutoWrapMode(const bool /*wrapAtEOL*/) override
{
Log::Comment(L"PrivateSetAutoWrapMode MOCK called...");
return false;
}
bool PrivateShowCursor(const bool show) override
{
Log::Comment(L"PrivateShowCursor MOCK called...");
if (_privateShowCursorResult)
{
VERIFY_ARE_EQUAL(_expectedShowCursor, show);
}
return _privateShowCursorResult;
}
bool PrivateAllowCursorBlinking(const bool enable) override
{
Log::Comment(L"PrivateAllowCursorBlinking MOCK called...");
if (_privateAllowCursorBlinkingResult)
{
VERIFY_ARE_EQUAL(_enable, enable);
}
return _privateAllowCursorBlinkingResult;
}
bool PrivateIsVtInputEnabled() const override
{
return false;
}
bool PrivateGetTextAttributes(TextAttribute& attrs) const
{
Log::Comment(L"PrivateGetTextAttributes MOCK called...");
if (_privateGetTextAttributesResult)
{
attrs = _attribute;
}
return _privateGetTextAttributesResult;
}
bool PrivateSetTextAttributes(const TextAttribute& attrs)
{
Log::Comment(L"PrivateSetTextAttributes MOCK called...");
if (_privateSetTextAttributesResult)
{
VERIFY_ARE_EQUAL(_expectedAttribute, attrs);
_attribute = attrs;
}
return _privateSetTextAttributesResult;
}
bool PrivateSetCurrentLineRendition(const LineRendition /*lineRendition*/)
{
Log::Comment(L"PrivateSetCurrentLineRendition MOCK called...");
return false;
}
bool PrivateResetLineRenditionRange(const size_t /*startRow*/, const size_t /*endRow*/)
{
Log::Comment(L"PrivateResetLineRenditionRange MOCK called...");
return false;
}
SHORT PrivateGetLineWidth(const size_t /*row*/) const
{
Log::Comment(L"PrivateGetLineWidth MOCK called...");
return _bufferSize.X;
}
bool PrivateWriteConsoleInputW(std::deque<std::unique_ptr<IInputEvent>>& events,
size_t& eventsWritten) override
{
Log::Comment(L"PrivateWriteConsoleInputW MOCK called...");
if (_privateWriteConsoleInputWResult)
{
// move all the input events we were given into local storage so we can test against them
Log::Comment(NoThrowString().Format(L"Moving %zu input events into local storage...", events.size()));
if (_retainInput)
{
std::move(events.begin(), events.end(), std::back_inserter(_events));
}
else
{
_events.clear();
_events.swap(events);
}
eventsWritten = _events.size();
}
return _privateWriteConsoleInputWResult;
}
bool PrivateWriteConsoleControlInput(_In_ KeyEvent key) override
{
Log::Comment(L"PrivateWriteConsoleControlInput MOCK called...");
if (_privateWriteConsoleControlInputResult)
{
VERIFY_ARE_EQUAL('C', key.GetVirtualKeyCode());
VERIFY_ARE_EQUAL(0x3, key.GetCharData());
VERIFY_ARE_EQUAL(true, key.IsCtrlPressed());
}
return _privateWriteConsoleControlInputResult;
}
bool PrivateSetScrollingRegion(const SMALL_RECT& scrollMargins) override
{
Log::Comment(L"PrivateSetScrollingRegion MOCK called...");
if (_privateSetScrollingRegionResult)
{
VERIFY_ARE_EQUAL(_expectedScrollRegion, scrollMargins);
}
return _privateSetScrollingRegionResult;
}
bool PrivateWarningBell() override
{
Log::Comment(L"PrivateWarningBell MOCK called...");
// We made it through the adapter, woo! Return true.
return TRUE;
}
bool PrivateGetLineFeedMode() const override
{
Log::Comment(L"PrivateGetLineFeedMode MOCK called...");
return _privateGetLineFeedModeResult;
}
bool PrivateLineFeed(const bool withReturn) override
{
Log::Comment(L"PrivateLineFeed MOCK called...");
if (_privateLineFeedResult)
{
VERIFY_ARE_EQUAL(_expectedLineFeedWithReturn, withReturn);
}
return _privateLineFeedResult;
}
bool PrivateReverseLineFeed() override
{
Log::Comment(L"PrivateReverseLineFeed MOCK called...");
// We made it through the adapter, woo! Return true.
return TRUE;
}
bool SetConsoleTitleW(const std::wstring_view title)
{
Log::Comment(L"SetConsoleTitleW MOCK called...");
if (_setConsoleTitleWResult)
{
// Put into WEX strings for rich logging when they don't compare.
VERIFY_ARE_EQUAL(String(_expectedWindowTitle.data(), gsl::narrow<int>(_expectedWindowTitle.size())),
String(title.data(), gsl::narrow<int>(title.size())));
}
return TRUE;
}
bool PrivateUseAlternateScreenBuffer() override
{
Log::Comment(L"PrivateUseAlternateScreenBuffer MOCK called...");
return true;
}
bool PrivateUseMainScreenBuffer() override
{
Log::Comment(L"PrivateUseMainScreenBuffer MOCK called...");
return true;
}
bool PrivateEraseAll() override
{
Log::Comment(L"PrivateEraseAll MOCK called...");
return TRUE;
}
bool PrivateClearBuffer() override
{
Log::Comment(L"PrivateClearBuffer MOCK called...");
return TRUE;
}
bool GetUserDefaultCursorStyle(CursorType& style) override
{
style = CursorType::Legacy;
return true;
}
bool SetCursorStyle(const CursorType cursorType) override
{
Log::Comment(L"SetCursorStyle MOCK called...");
if (_setCursorStyleResult)
{
VERIFY_ARE_EQUAL(_expectedCursorStyle, cursorType);
}
return _setCursorStyleResult;
}
bool SetCursorColor(const COLORREF cursorColor) override
{
Log::Comment(L"SetCursorColor MOCK called...");
if (_setCursorColorResult)
{
VERIFY_ARE_EQUAL(_expectedCursorColor, cursorColor);
}
return _setCursorColorResult;
}
bool PrivateRefreshWindow() override
{
Log::Comment(L"PrivateRefreshWindow MOCK called...");
// We made it through the adapter, woo! Return true.
return TRUE;
}
bool PrivateSuppressResizeRepaint() override
{
Log::Comment(L"PrivateSuppressResizeRepaint MOCK called...");
VERIFY_IS_TRUE(false, L"AdaptDispatch should never be calling this function.");
return FALSE;
}
bool SetConsoleOutputCP(const unsigned int /*codepage*/) override
{
Log::Comment(L"SetConsoleOutputCP MOCK called...");
return TRUE;
}
bool GetConsoleOutputCP(unsigned int& codepage) override
{
Log::Comment(L"GetConsoleOutputCP MOCK called...");
if (_getConsoleOutputCPResult)
{
codepage = _expectedOutputCP;
}
return _getConsoleOutputCPResult;
}
bool IsConsolePty() const override
{
Log::Comment(L"IsConsolePty MOCK called...");
return _isPty;
}
bool DeleteLines(const size_t /*count*/) override
{
Log::Comment(L"DeleteLines MOCK called...");
return TRUE;
}
bool InsertLines(const size_t /*count*/) override
{
Log::Comment(L"InsertLines MOCK called...");
return TRUE;
}
bool MoveToBottom() const override
{
Log::Comment(L"MoveToBottom MOCK called...");
return _moveToBottomResult;
}
bool PrivateGetColorTableEntry(const size_t index, COLORREF& value) const noexcept override
{
Log::Comment(L"PrivateGetColorTableEntry MOCK called...");
if (_privateGetColorTableEntryResult)
{
VERIFY_ARE_EQUAL(_expectedColorTableIndex, index);
// Simply returning the index as the color value makes it easy for
// tests to confirm that they've received the color they expected.
value = gsl::narrow_cast<COLORREF>(index);
}
return _privateGetColorTableEntryResult;
}
bool PrivateSetColorTableEntry(const size_t index, const COLORREF value) const noexcept override
{
Log::Comment(L"PrivateSetColorTableEntry MOCK called...");
if (_privateSetColorTableEntryResult)
{
VERIFY_ARE_EQUAL(_expectedColorTableIndex, index);
VERIFY_ARE_EQUAL(_expectedColorValue, value);
}
return _privateSetColorTableEntryResult;
}
bool PrivateSetDefaultForeground(const COLORREF value) const noexcept override
{
Log::Comment(L"PrivateSetDefaultForeground MOCK called...");
if (_privateSetDefaultForegroundResult)
{
VERIFY_ARE_EQUAL(_expectedDefaultForegroundColorValue, value);
}
return _privateSetDefaultForegroundResult;
}
bool PrivateSetDefaultBackground(const COLORREF value) const noexcept override
{
Log::Comment(L"PrivateSetDefaultForeground MOCK called...");
if (_privateSetDefaultBackgroundResult)
{
VERIFY_ARE_EQUAL(_expectedDefaultBackgroundColorValue, value);
}
return _privateSetDefaultBackgroundResult;
}
bool PrivateFillRegion(const COORD /*startPosition*/,
const size_t /*fillLength*/,
const wchar_t /*fillChar*/,
const bool /*standardFillAttrs*/) noexcept override
{
Log::Comment(L"PrivateFillRegion MOCK called...");
return TRUE;
}
bool PrivateScrollRegion(const SMALL_RECT /*scrollRect*/,
const std::optional<SMALL_RECT> /*clipRect*/,
const COORD /*destinationOrigin*/,
const bool /*standardFillAttrs*/) noexcept override
{
Log::Comment(L"PrivateScrollRegion MOCK called...");
return TRUE;
}
bool PrivateUpdateSoftFont(const gsl::span<const uint16_t> /*bitPattern*/,
const SIZE cellSize,
const size_t /*centeringHint*/) noexcept override
{
Log::Comment(L"PrivateUpdateSoftFont MOCK called...");
Log::Comment(NoThrowString().Format(L"Cell size: %dx%d", cellSize.cx, cellSize.cy));
VERIFY_ARE_EQUAL(_expectedCellSize.cx, cellSize.cx);
VERIFY_ARE_EQUAL(_expectedCellSize.cy, cellSize.cy);
return TRUE;
}
void PrepData()
{
PrepData(CursorDirection::UP); // if called like this, the cursor direction doesn't matter.
}
void PrepData(CursorDirection dir)
{
switch (dir)
{
case CursorDirection::UP:
return PrepData(CursorX::LEFT, CursorY::TOP);
case CursorDirection::DOWN:
return PrepData(CursorX::LEFT, CursorY::BOTTOM);
case CursorDirection::LEFT:
return PrepData(CursorX::LEFT, CursorY::TOP);
case CursorDirection::RIGHT:
return PrepData(CursorX::RIGHT, CursorY::TOP);
case CursorDirection::NEXTLINE:
return PrepData(CursorX::LEFT, CursorY::BOTTOM);
case CursorDirection::PREVLINE:
return PrepData(CursorX::LEFT, CursorY::TOP);
}
}
void PrepData(CursorX xact, CursorY yact)
{
Log::Comment(L"Resetting mock data state.");
// APIs succeed by default
_setConsoleCursorPositionResult = TRUE;
_getConsoleScreenBufferInfoExResult = TRUE;
_privateGetTextAttributesResult = TRUE;
_privateSetTextAttributesResult = TRUE;
_privateWriteConsoleInputWResult = TRUE;
_privateWriteConsoleControlInputResult = TRUE;
_setConsoleWindowInfoResult = TRUE;
_moveToBottomResult = true;
_bufferSize.X = 100;
_bufferSize.Y = 600;
// Viewport sitting in the "middle" of the buffer somewhere (so all sides have excess buffer around them)
_viewport.Top = 20;
_viewport.Bottom = 49;
_viewport.Left = 30;
_viewport.Right = 59;
// Call cursor positions separately
PrepCursor(xact, yact);
_cursorVisible = TRUE;
// Attribute default is gray on black.
_attribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
_expectedAttribute = _attribute;
_events.clear();
_retainInput = false;
}
void PrepCursor(CursorX xact, CursorY yact)
{
Log::Comment(L"Adjusting cursor within viewport... Expected will match actual when done.");
switch (xact)
{
case CursorX::LEFT:
Log::Comment(L"Cursor set to left edge of buffer.");
_cursorPos.X = 0;
break;
case CursorX::RIGHT:
Log::Comment(L"Cursor set to right edge of buffer.");
_cursorPos.X = _bufferSize.X - 1;
break;
case CursorX::XCENTER:
Log::Comment(L"Cursor set to centered X of buffer.");
_cursorPos.X = _bufferSize.X / 2;
break;
}
switch (yact)
{
case CursorY::TOP:
Log::Comment(L"Cursor set to top edge of viewport.");
_cursorPos.Y = _viewport.Top;
break;
case CursorY::BOTTOM:
Log::Comment(L"Cursor set to bottom edge of viewport.");
_cursorPos.Y = _viewport.Bottom - 1;
break;
case CursorY::YCENTER:
Log::Comment(L"Cursor set to centered Y of viewport.");
_cursorPos.Y = _viewport.Top + ((_viewport.Bottom - _viewport.Top) / 2);
break;
}
_expectedCursorPos = _cursorPos;
}
void ValidateInputEvent(_In_ PCWSTR pwszExpectedResponse)
{
size_t const cchResponse = wcslen(pwszExpectedResponse);
size_t const eventCount = _events.size();
VERIFY_ARE_EQUAL(cchResponse * 2, eventCount, L"We should receive TWO input records for every character in the expected string. Key down and key up.");
for (size_t iInput = 0; iInput < eventCount; iInput++)
{
wchar_t const wch = pwszExpectedResponse[iInput / 2]; // the same portion of the string will be used twice. 0/2 = 0. 1/2 = 0. 2/2 = 1. 3/2 = 1. and so on.
VERIFY_ARE_EQUAL(InputEventType::KeyEvent, _events[iInput]->EventType());
const KeyEvent* const keyEvent = static_cast<const KeyEvent* const>(_events[iInput].get());
// every even key is down. every odd key is up. DOWN = 0, UP = 1. DOWN = 2, UP = 3. and so on.
VERIFY_ARE_EQUAL((bool)!(iInput % 2), keyEvent->IsKeyDown());
VERIFY_ARE_EQUAL(0u, keyEvent->GetActiveModifierKeys());
Log::Comment(NoThrowString().Format(L"Comparing '%c' with '%c'...", wch, keyEvent->GetCharData()));
VERIFY_ARE_EQUAL(wch, keyEvent->GetCharData());
VERIFY_ARE_EQUAL(1u, keyEvent->GetRepeatCount());
VERIFY_ARE_EQUAL(0u, keyEvent->GetVirtualKeyCode());
VERIFY_ARE_EQUAL(0u, keyEvent->GetVirtualScanCode());
}
}
bool PrivateAddHyperlink(const std::wstring_view /*uri*/, const std::wstring_view /*params*/) const
{
Log::Comment(L"PrivateAddHyperlink MOCK called...");
return TRUE;
}
bool PrivateEndHyperlink() const
{
Log::Comment(L"PrivateEndHyperlink MOCK called...");
return TRUE;
}
void _SetMarginsHelper(SMALL_RECT* rect, SHORT top, SHORT bottom)
{
rect->Top = top;
rect->Bottom = bottom;
//The rectangle is going to get converted from VT space to conhost space
_expectedScrollRegion.Top = (top > 0) ? rect->Top - 1 : rect->Top;
_expectedScrollRegion.Bottom = (bottom > 0) ? rect->Bottom - 1 : rect->Bottom;
}
~TestGetSet()
{
}
static const WCHAR s_wchErase = (WCHAR)0x20;
static const WCHAR s_wchDefault = L'Z';
static const WORD s_wAttrErase = FOREGROUND_BLUE | FOREGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY;
static const WORD s_wDefaultAttribute = 0;
static const WORD s_defaultFill = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED; // dark gray on black.
std::deque<std::unique_ptr<IInputEvent>> _events;
bool _retainInput{ false };
auto EnableInputRetentionInScope()
{
auto oldRetainValue{ _retainInput };
_retainInput = true;
return wil::scope_exit([oldRetainValue, this] {
_retainInput = oldRetainValue;
});
}
COORD _bufferSize = { 0, 0 };
SMALL_RECT _viewport = { 0, 0, 0, 0 };
SMALL_RECT _expectedConsoleWindow = { 0, 0, 0, 0 };
COORD _cursorPos = { 0, 0 };
SMALL_RECT _expectedScrollRegion = { 0, 0, 0, 0 };
bool _cursorVisible = false;
COORD _expectedCursorPos = { 0, 0 };
TextAttribute _attribute = {};
TextAttribute _expectedAttribute = {};
unsigned int _expectedOutputCP = 0;
bool _isPty = false;
bool _privateShowCursorResult = false;
bool _expectedShowCursor = false;
bool _getConsoleScreenBufferInfoExResult = false;
bool _setConsoleCursorPositionResult = false;
bool _privateGetTextAttributesResult = false;
bool _privateSetTextAttributesResult = false;
bool _privateWriteConsoleInputWResult = false;
bool _privateWriteConsoleControlInputResult = false;
bool _setConsoleWindowInfoResult = false;
bool _expectedWindowAbsolute = false;
bool _setConsoleScreenBufferInfoExResult = false;
COORD _expectedScreenBufferSize = { 0, 0 };
SMALL_RECT _expectedScreenBufferViewport{ 0, 0, 0, 0 };
bool _setInputModeResult = false;
TerminalInput::Mode _expectedInputMode;
bool _expectedInputModeEnabled = false;
bool _privateSetAnsiModeResult = false;
bool _expectedAnsiMode = false;
bool _privateAllowCursorBlinkingResult = false;
bool _enable = false; // for cursor blinking
bool _privateSetScrollingRegionResult = false;
bool _privateGetLineFeedModeResult = false;
bool _privateLineFeedResult = false;
bool _expectedLineFeedWithReturn = false;
bool _privateReverseLineFeedResult = false;
bool _setConsoleTitleWResult = false;
std::wstring_view _expectedWindowTitle{};
bool _setCursorStyleResult = false;
CursorType _expectedCursorStyle;
bool _setCursorColorResult = false;
COLORREF _expectedCursorColor = 0;
bool _getConsoleOutputCPResult = false;
bool _moveToBottomResult = false;
bool _privateGetColorTableEntryResult = false;
bool _privateSetColorTableEntryResult = false;
size_t _expectedColorTableIndex = SIZE_MAX;
COLORREF _expectedColorValue = INVALID_COLOR;
bool _privateSetDefaultForegroundResult = false;
COLORREF _expectedDefaultForegroundColorValue = INVALID_COLOR;
bool _privateSetDefaultBackgroundResult = false;
COLORREF _expectedDefaultBackgroundColorValue = INVALID_COLOR;
SIZE _expectedCellSize = {};
private:
HANDLE _hCon;
};
class DummyAdapter : public AdaptDefaults
{
void Print(const wchar_t /*wch*/) override
{
}
void PrintString(const std::wstring_view /*string*/) override
{
}
void Execute(const wchar_t /*wch*/) override
{
}
};
class AdapterTest
{
public:
TEST_CLASS(AdapterTest);
TEST_METHOD_SETUP(SetupMethods)
{
bool fSuccess = true;
auto api = std::make_unique<TestGetSet>();
fSuccess = api.get() != nullptr;
if (fSuccess)
{
auto adapter = std::make_unique<DummyAdapter>();
// give AdaptDispatch ownership of _testGetSet
_testGetSet = api.get(); // keep a copy for us but don't manage its lifetime anymore.
_pDispatch = std::make_unique<AdaptDispatch>(std::move(api), std::move(adapter));
fSuccess = _pDispatch != nullptr;
}
return fSuccess;
}
TEST_METHOD_CLEANUP(CleanupMethods)
{
_pDispatch.reset();
_testGetSet = nullptr;
return true;
}
TEST_METHOD(CursorMovementTest)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiDirection", L"{0, 1, 2, 3, 4, 5}") // These values align with the CursorDirection enum class to try all the directions.
END_TEST_METHOD_PROPERTIES()
Log::Comment(L"Starting test...");
// Used to switch between the various function options.
typedef bool (AdaptDispatch::*CursorMoveFunc)(size_t);
CursorMoveFunc moveFunc = nullptr;
// Modify variables based on directionality of this test
CursorDirection direction;
size_t dir;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDirection", dir));
direction = (CursorDirection)dir;
switch (direction)
{
case CursorDirection::UP:
Log::Comment(L"Testing up direction.");
moveFunc = &AdaptDispatch::CursorUp;
break;
case CursorDirection::DOWN:
Log::Comment(L"Testing down direction.");
moveFunc = &AdaptDispatch::CursorDown;
break;
case CursorDirection::RIGHT:
Log::Comment(L"Testing right direction.");
moveFunc = &AdaptDispatch::CursorForward;
break;
case CursorDirection::LEFT:
Log::Comment(L"Testing left direction.");
moveFunc = &AdaptDispatch::CursorBackward;
break;
case CursorDirection::NEXTLINE:
Log::Comment(L"Testing next line direction.");
moveFunc = &AdaptDispatch::CursorNextLine;
break;
case CursorDirection::PREVLINE:
Log::Comment(L"Testing prev line direction.");
moveFunc = &AdaptDispatch::CursorPrevLine;
break;
}
if (moveFunc == nullptr)
{
VERIFY_FAIL();
return;
}
// success cases
// place cursor in top left. moving up is expected to go nowhere (it should get bounded by the viewport)
Log::Comment(L"Test 1: Cursor doesn't move when placed in corner of viewport.");
_testGetSet->PrepData(direction);
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
Log::Comment(L"Test 1b: Cursor moves to left of line with next/prev line command when cursor can't move higher/lower.");
bool fDoTest1b = false;
switch (direction)
{
case CursorDirection::NEXTLINE:
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
fDoTest1b = true;
break;
case CursorDirection::PREVLINE:
_testGetSet->PrepData(CursorX::RIGHT, CursorY::TOP);
fDoTest1b = true;
break;
}
if (fDoTest1b)
{
_testGetSet->_expectedCursorPos.X = 0;
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
}
else
{
Log::Comment(L"Test not applicable to direction selected. Skipping.");
}
// place cursor lower, move up 1.
Log::Comment(L"Test 2: Cursor moves 1 in the correct direction from viewport.");
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
switch (direction)
{
case CursorDirection::UP:
_testGetSet->_expectedCursorPos.Y--;
break;
case CursorDirection::DOWN:
_testGetSet->_expectedCursorPos.Y++;
break;
case CursorDirection::RIGHT:
_testGetSet->_expectedCursorPos.X++;
break;
case CursorDirection::LEFT:
_testGetSet->_expectedCursorPos.X--;
break;
case CursorDirection::NEXTLINE:
_testGetSet->_expectedCursorPos.Y++;
_testGetSet->_expectedCursorPos.X = 0;
break;
case CursorDirection::PREVLINE:
_testGetSet->_expectedCursorPos.Y--;
_testGetSet->_expectedCursorPos.X = 0;
break;
}
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
// place cursor and move it up too far. It should get bounded by the viewport.
Log::Comment(L"Test 3: Cursor moves and gets stuck at viewport when started away from edges and moved beyond edges.");
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
// Bottom and right viewports are -1 because those two sides are specified to be 1 outside the viewable area.
switch (direction)
{
case CursorDirection::UP:
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
break;
case CursorDirection::DOWN:
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
break;
case CursorDirection::RIGHT:
_testGetSet->_expectedCursorPos.X = _testGetSet->_bufferSize.X - 1;
break;
case CursorDirection::LEFT:
_testGetSet->_expectedCursorPos.X = 0;
break;
case CursorDirection::NEXTLINE:
_testGetSet->_expectedCursorPos.X = 0;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
break;
case CursorDirection::PREVLINE:
_testGetSet->_expectedCursorPos.X = 0;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
break;
}
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(100));
// error cases
// SetConsoleCursorPosition throws failure. Parameters are otherwise normal.
Log::Comment(L"Test 4: When SetConsoleCursorPosition throws a failure, call fails and cursor doesn't move.");
_testGetSet->PrepData(direction);
_testGetSet->_setConsoleCursorPositionResult = FALSE;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
// GetConsoleScreenBufferInfo throws failure. Parameters are otherwise normal.
Log::Comment(L"Test 5: When GetConsoleScreenBufferInfo throws a failure, call fails and cursor doesn't move.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
}
TEST_METHOD(CursorPositionTest)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Place cursor within the viewport. Start from top left, move to middle.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
short sCol = (_testGetSet->_viewport.Right - _testGetSet->_viewport.Left) / 2;
short sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) / 2;
// The X coordinate is unaffected by the viewport.
_testGetSet->_expectedCursorPos.X = sCol - 1;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top + (sRow - 1);
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
Log::Comment(L"Test 2: Move to 0, 0 (which is 1,1 in VT speak)");
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
// The X coordinate is unaffected by the viewport.
_testGetSet->_expectedCursorPos.X = 0;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(1, 1));
Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
sCol = (_testGetSet->_bufferSize.X) * 2;
sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) * 2;
_testGetSet->_expectedCursorPos.X = _testGetSet->_bufferSize.X - 1;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
Log::Comment(L"Test 4: GetConsoleInfo API returns false. No move, return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(1, 1));
Log::Comment(L"Test 5: SetCursor API returns false. No move, return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_setConsoleCursorPositionResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(1, 1));
}
TEST_METHOD(CursorSingleDimensionMoveTest)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiDirection", L"{0, 1}") // These values align with the CursorDirection enum class to try all the directions.
END_TEST_METHOD_PROPERTIES()
Log::Comment(L"Starting test...");
//// Used to switch between the various function options.
typedef bool (AdaptDispatch::*CursorMoveFunc)(size_t);
CursorMoveFunc moveFunc = nullptr;
SHORT sRangeEnd = 0;
SHORT sRangeStart = 0;
SHORT* psCursorExpected = nullptr;
// Modify variables based on directionality of this test
AbsolutePosition direction;
size_t dir;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDirection", dir));
direction = (AbsolutePosition)dir;
_testGetSet->PrepData();
switch (direction)
{
case AbsolutePosition::CursorHorizontal:
Log::Comment(L"Testing cursor horizontal movement.");
sRangeEnd = _testGetSet->_bufferSize.X;
sRangeStart = 0;
psCursorExpected = &_testGetSet->_expectedCursorPos.X;
moveFunc = &AdaptDispatch::CursorHorizontalPositionAbsolute;
break;
case AbsolutePosition::VerticalLine:
Log::Comment(L"Testing vertical line movement.");
sRangeEnd = _testGetSet->_viewport.Bottom;
sRangeStart = _testGetSet->_viewport.Top;
psCursorExpected = &_testGetSet->_expectedCursorPos.Y;
moveFunc = &AdaptDispatch::VerticalLinePositionAbsolute;
break;
}
if (moveFunc == nullptr || psCursorExpected == nullptr)
{
VERIFY_FAIL();
return;
}
Log::Comment(L"Test 1: Place cursor within the viewport. Start from top left, move to middle.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
short sVal = (sRangeEnd - sRangeStart) / 2;
*psCursorExpected = sRangeStart + (sVal - 1);
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 2: Move to 0 (which is 1 in VT speak)");
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
*psCursorExpected = sRangeStart;
sVal = 1;
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
sVal = (sRangeEnd - sRangeStart) * 2;
*psCursorExpected = sRangeEnd - 1;
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 4: GetConsoleInfo API returns false. No move, return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
sVal = 1;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 5: SetCursor API returns false. No move, return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_setConsoleCursorPositionResult = FALSE;
sVal = 1;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
}
TEST_METHOD(CursorSaveRestoreTest)
{
Log::Comment(L"Starting test...");
COORD coordExpected = { 0 };
Log::Comment(L"Test 1: Restore with no saved data should move to top-left corner, the null/default position.");
// Move cursor to top left and save off expected position.
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
coordExpected = _testGetSet->_expectedCursorPos;
// Then move cursor to the middle and reset the expected to the top left.
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
_testGetSet->_expectedCursorPos = coordExpected;
// Attributes are restored to defaults.
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch.get()->CursorRestoreState(), L"By default, restore to top left corner (0,0 offset from viewport).");
Log::Comment(L"Test 2: Place cursor in center. Save. Move cursor to corner. Restore. Should come back to center.");
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
VERIFY_IS_TRUE(_pDispatch.get()->CursorSaveState(), L"Succeed at saving position.");
Log::Comment(L"Backup expected cursor (in the middle). Move cursor to corner. Then re-set expected cursor to middle.");
// save expected cursor position
coordExpected = _testGetSet->_expectedCursorPos;
// adjust cursor to corner
_testGetSet->PrepData(CursorX::LEFT, CursorY::BOTTOM);
// restore expected cursor position to center.
_testGetSet->_expectedCursorPos = coordExpected;
VERIFY_IS_TRUE(_pDispatch.get()->CursorRestoreState(), L"Restoring to corner should succeed. API call inside will test that cursor matched expected position.");
}
TEST_METHOD(CursorHideShowTest)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:fStartingVis", L"{TRUE, FALSE}")
TEST_METHOD_PROPERTY(L"Data:fEndingVis", L"{TRUE, FALSE}")
END_TEST_METHOD_PROPERTIES()
Log::Comment(L"Starting test...");
// Modify variables based on permutations of this test.
bool fStart;
bool fEnd;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"fStartingVis", fStart));
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"fEndingVis", fEnd));
Log::Comment(L"Test 1: Verify successful API call modifies visibility state.");
_testGetSet->PrepData();
_testGetSet->_cursorVisible = fStart;
_testGetSet->_privateShowCursorResult = true;
_testGetSet->_expectedShowCursor = fEnd;
VERIFY_IS_TRUE(_pDispatch.get()->CursorVisibility(fEnd));
Log::Comment(L"Test 3: When we fail to set updated cursor information, the dispatch should fail.");
_testGetSet->PrepData();
_testGetSet->_privateShowCursorResult = false;
VERIFY_IS_FALSE(_pDispatch.get()->CursorVisibility(fEnd));
}
TEST_METHOD(GraphicsBaseTests)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Send no options.");
_testGetSet->PrepData();
VTParameter rgOptions[16];
size_t cOptions = 0;
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
Log::Comment(L"Test 2: Gracefully fail when getting attribute data fails.");
_testGetSet->PrepData();
_testGetSet->_privateGetTextAttributesResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
Log::Comment(L"Test 3: Gracefully fail when setting attribute data fails.");
_testGetSet->PrepData();
_testGetSet->_privateSetTextAttributesResult = FALSE;
// Need at least one option in order for the call to be able to fail.
rgOptions[0] = (DispatchTypes::GraphicsOptions)0;
cOptions = 1;
VERIFY_IS_FALSE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
}
TEST_METHOD(GraphicsSingleTests)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiGraphicsOptions", L"{0, 1, 2, 4, 7, 8, 9, 21, 22, 24, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 53, 55, 90, 91, 92, 93, 94, 95, 96, 97, 100, 101, 102, 103, 104, 105, 106, 107}") // corresponds to options in DispatchTypes::GraphicsOptions
END_TEST_METHOD_PROPERTIES()
Log::Comment(L"Starting test...");
_testGetSet->PrepData();
// Modify variables based on type of this test
DispatchTypes::GraphicsOptions graphicsOption;
size_t uiGraphicsOption;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiGraphicsOptions", uiGraphicsOption));
graphicsOption = (DispatchTypes::GraphicsOptions)uiGraphicsOption;
VTParameter rgOptions[16];
size_t cOptions = 1;
rgOptions[0] = graphicsOption;
switch (graphicsOption)
{
case DispatchTypes::GraphicsOptions::Off:
Log::Comment(L"Testing graphics 'Off/Reset'");
_testGetSet->_attribute = TextAttribute{ (WORD)~_testGetSet->s_defaultFill };
_testGetSet->_expectedAttribute = TextAttribute{};
break;
case DispatchTypes::GraphicsOptions::BoldBright:
Log::Comment(L"Testing graphics 'Bold/Bright'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute.SetBold(true);
break;
case DispatchTypes::GraphicsOptions::RGBColorOrFaint:
Log::Comment(L"Testing graphics 'Faint'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute.SetFaint(true);
break;
case DispatchTypes::GraphicsOptions::Underline:
Log::Comment(L"Testing graphics 'Underline'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute.SetUnderlined(true);
break;
case DispatchTypes::GraphicsOptions::DoublyUnderlined:
Log::Comment(L"Testing graphics 'Doubly Underlined'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute.SetDoublyUnderlined(true);
break;
case DispatchTypes::GraphicsOptions::Overline:
Log::Comment(L"Testing graphics 'Overline'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = TextAttribute{ COMMON_LVB_GRID_HORIZONTAL };
break;
case DispatchTypes::GraphicsOptions::Negative:
Log::Comment(L"Testing graphics 'Negative'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = TextAttribute{ COMMON_LVB_REVERSE_VIDEO };
break;
case DispatchTypes::GraphicsOptions::Invisible:
Log::Comment(L"Testing graphics 'Invisible'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute.SetInvisible(true);
break;
case DispatchTypes::GraphicsOptions::CrossedOut:
Log::Comment(L"Testing graphics 'Crossed Out'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute.SetCrossedOut(true);
break;
case DispatchTypes::GraphicsOptions::NotBoldOrFaint:
Log::Comment(L"Testing graphics 'No Bold or Faint'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_attribute.SetBold(true);
_testGetSet->_attribute.SetFaint(true);
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
break;
case DispatchTypes::GraphicsOptions::NoUnderline:
Log::Comment(L"Testing graphics 'No Underline'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_attribute.SetUnderlined(true);
_testGetSet->_attribute.SetDoublyUnderlined(true);
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
break;
case DispatchTypes::GraphicsOptions::NoOverline:
Log::Comment(L"Testing graphics 'No Overline'");
_testGetSet->_attribute = TextAttribute{ COMMON_LVB_GRID_HORIZONTAL };
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
break;
case DispatchTypes::GraphicsOptions::Positive:
Log::Comment(L"Testing graphics 'Positive'");
_testGetSet->_attribute = TextAttribute{ COMMON_LVB_REVERSE_VIDEO };
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
break;
case DispatchTypes::GraphicsOptions::Visible:
Log::Comment(L"Testing graphics 'Visible'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_attribute.SetInvisible(true);
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
break;
case DispatchTypes::GraphicsOptions::NotCrossedOut:
Log::Comment(L"Testing graphics 'Not Crossed Out'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_attribute.SetCrossedOut(true);
_testGetSet->_expectedAttribute = TextAttribute{ 0 };
break;
case DispatchTypes::GraphicsOptions::ForegroundBlack:
Log::Comment(L"Testing graphics 'Foreground Color Black'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLACK);
break;
case DispatchTypes::GraphicsOptions::ForegroundBlue:
Log::Comment(L"Testing graphics 'Foreground Color Blue'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
break;
case DispatchTypes::GraphicsOptions::ForegroundGreen:
Log::Comment(L"Testing graphics 'Foreground Color Green'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
break;
case DispatchTypes::GraphicsOptions::ForegroundCyan:
Log::Comment(L"Testing graphics 'Foreground Color Cyan'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_CYAN);
break;
case DispatchTypes::GraphicsOptions::ForegroundRed:
Log::Comment(L"Testing graphics 'Foreground Color Red'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_RED);
break;
case DispatchTypes::GraphicsOptions::ForegroundMagenta:
Log::Comment(L"Testing graphics 'Foreground Color Magenta'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_GREEN | FOREGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_MAGENTA);
break;
case DispatchTypes::GraphicsOptions::ForegroundYellow:
Log::Comment(L"Testing graphics 'Foreground Color Yellow'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_YELLOW);
break;
case DispatchTypes::GraphicsOptions::ForegroundWhite:
Log::Comment(L"Testing graphics 'Foreground Color White'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_WHITE);
break;
case DispatchTypes::GraphicsOptions::ForegroundDefault:
Log::Comment(L"Testing graphics 'Foreground Color Default'");
_testGetSet->_attribute = TextAttribute{ (WORD)~_testGetSet->s_wDefaultAttribute }; // set the current attribute to the opposite of default so we can ensure all relevant bits flip.
// To get expected value, take what we started with and change ONLY the background series of bits to what the Default says.
_testGetSet->_expectedAttribute = _testGetSet->_attribute; // expect = starting
_testGetSet->_expectedAttribute.SetDefaultForeground(); // set the foreground as default
break;
case DispatchTypes::GraphicsOptions::BackgroundBlack:
Log::Comment(L"Testing graphics 'Background Color Black'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_BLACK);
break;
case DispatchTypes::GraphicsOptions::BackgroundBlue:
Log::Comment(L"Testing graphics 'Background Color Blue'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_BLUE);
break;
case DispatchTypes::GraphicsOptions::BackgroundGreen:
Log::Comment(L"Testing graphics 'Background Color Green'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_GREEN);
break;
case DispatchTypes::GraphicsOptions::BackgroundCyan:
Log::Comment(L"Testing graphics 'Background Color Cyan'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_CYAN);
break;
case DispatchTypes::GraphicsOptions::BackgroundRed:
Log::Comment(L"Testing graphics 'Background Color Red'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_RED);
break;
case DispatchTypes::GraphicsOptions::BackgroundMagenta:
Log::Comment(L"Testing graphics 'Background Color Magenta'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_GREEN | BACKGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_MAGENTA);
break;
case DispatchTypes::GraphicsOptions::BackgroundYellow:
Log::Comment(L"Testing graphics 'Background Color Yellow'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_YELLOW);
break;
case DispatchTypes::GraphicsOptions::BackgroundWhite:
Log::Comment(L"Testing graphics 'Background Color White'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_INTENSITY };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_WHITE);
break;
case DispatchTypes::GraphicsOptions::BackgroundDefault:
Log::Comment(L"Testing graphics 'Background Color Default'");
_testGetSet->_attribute = TextAttribute{ (WORD)~_testGetSet->s_wDefaultAttribute }; // set the current attribute to the opposite of default so we can ensure all relevant bits flip.
// To get expected value, take what we started with and change ONLY the background series of bits to what the Default says.
_testGetSet->_expectedAttribute = _testGetSet->_attribute; // expect = starting
_testGetSet->_expectedAttribute.SetDefaultBackground(); // set the background as default
break;
case DispatchTypes::GraphicsOptions::BrightForegroundBlack:
Log::Comment(L"Testing graphics 'Bright Foreground Color Black'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_BLACK);
break;
case DispatchTypes::GraphicsOptions::BrightForegroundBlue:
Log::Comment(L"Testing graphics 'Bright Foreground Color Blue'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_GREEN };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_BLUE);
break;
case DispatchTypes::GraphicsOptions::BrightForegroundGreen:
Log::Comment(L"Testing graphics 'Bright Foreground Color Green'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_RED | FOREGROUND_BLUE };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_GREEN);
break;
case DispatchTypes::GraphicsOptions::BrightForegroundCyan:
Log::Comment(L"Testing graphics 'Bright Foreground Color Cyan'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_RED };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_CYAN);
break;
case DispatchTypes::GraphicsOptions::BrightForegroundRed:
Log::Comment(L"Testing graphics 'Bright Foreground Color Red'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_BLUE | FOREGROUND_GREEN };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_RED);
break;
case DispatchTypes::GraphicsOptions::BrightForegroundMagenta:
Log::Comment(L"Testing graphics 'Bright Foreground Color Magenta'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_GREEN };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_MAGENTA);
break;
case DispatchTypes::GraphicsOptions::BrightForegroundYellow:
Log::Comment(L"Testing graphics 'Bright Foreground Color Yellow'");
_testGetSet->_attribute = TextAttribute{ FOREGROUND_BLUE };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_YELLOW);
break;
case DispatchTypes::GraphicsOptions::BrightForegroundWhite:
Log::Comment(L"Testing graphics 'Bright Foreground Color White'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_WHITE);
break;
case DispatchTypes::GraphicsOptions::BrightBackgroundBlack:
Log::Comment(L"Testing graphics 'Bright Background Color Black'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_BLACK);
break;
case DispatchTypes::GraphicsOptions::BrightBackgroundBlue:
Log::Comment(L"Testing graphics 'Bright Background Color Blue'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_GREEN };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_BLUE);
break;
case DispatchTypes::GraphicsOptions::BrightBackgroundGreen:
Log::Comment(L"Testing graphics 'Bright Background Color Green'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_RED | BACKGROUND_BLUE };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_GREEN);
break;
case DispatchTypes::GraphicsOptions::BrightBackgroundCyan:
Log::Comment(L"Testing graphics 'Bright Background Color Cyan'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_RED };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_CYAN);
break;
case DispatchTypes::GraphicsOptions::BrightBackgroundRed:
Log::Comment(L"Testing graphics 'Bright Background Color Red'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_BLUE | BACKGROUND_GREEN };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_RED);
break;
case DispatchTypes::GraphicsOptions::BrightBackgroundMagenta:
Log::Comment(L"Testing graphics 'Bright Background Color Magenta'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_GREEN };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_MAGENTA);
break;
case DispatchTypes::GraphicsOptions::BrightBackgroundYellow:
Log::Comment(L"Testing graphics 'Bright Background Color Yellow'");
_testGetSet->_attribute = TextAttribute{ BACKGROUND_BLUE };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_YELLOW);
break;
case DispatchTypes::GraphicsOptions::BrightBackgroundWhite:
Log::Comment(L"Testing graphics 'Bright Background Color White'");
_testGetSet->_attribute = TextAttribute{ 0 };
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::BRIGHT_WHITE);
break;
default:
VERIFY_FAIL(L"Test not implemented yet!");
break;
}
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
}
TEST_METHOD(GraphicsPushPopTests)
{
Log::Comment(L"Starting test...");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
VTParameter rgOptions[16];
VTParameter rgStackOptions[16];
size_t cOptions = 1;
Log::Comment(L"Test 1: Basic push and pop");
rgOptions[0] = DispatchTypes::GraphicsOptions::Off;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
cOptions = 0;
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());
Log::Comment(L"Test 2: Push, change color, pop");
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));
cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundCyan;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_CYAN);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
cOptions = 0;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());
Log::Comment(L"Test 3: two pushes (nested) and pops");
// First push:
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));
cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundRed;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_RED);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
// Second push:
cOptions = 0;
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));
cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundGreen;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
// First pop:
cOptions = 0;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_RED);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());
// Second pop:
cOptions = 0;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());
Log::Comment(L"Test 4: Save and restore partial attributes");
cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundGreen;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::BoldBright;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
_testGetSet->_expectedAttribute.SetBold(true);
_testGetSet->_expectedAttribute.SetDefaultBackground();
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundBlue;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_BLUE);
_testGetSet->_expectedAttribute.SetBold(true);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
// Push, specifying that we only want to save the background, the boldness, and double-underline-ness:
cOptions = 3;
rgStackOptions[0] = (size_t)DispatchTypes::SgrSaveRestoreStackOptions::Boldness;
rgStackOptions[1] = (size_t)DispatchTypes::SgrSaveRestoreStackOptions::SaveBackgroundColor;
rgStackOptions[2] = (size_t)DispatchTypes::SgrSaveRestoreStackOptions::DoublyUnderlined;
VERIFY_IS_TRUE(_pDispatch->PushGraphicsRendition({ rgStackOptions, cOptions }));
// Now change everything...
cOptions = 2;
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundGreen;
rgOptions[1] = DispatchTypes::GraphicsOptions::DoublyUnderlined;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_GREEN);
_testGetSet->_expectedAttribute.SetBold(true);
_testGetSet->_expectedAttribute.SetDoublyUnderlined(true);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
cOptions = 1;
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundRed;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_RED);
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_GREEN);
_testGetSet->_expectedAttribute.SetBold(true);
_testGetSet->_expectedAttribute.SetDoublyUnderlined(true);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
rgOptions[0] = DispatchTypes::GraphicsOptions::NotBoldOrFaint;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_RED);
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_GREEN);
_testGetSet->_expectedAttribute.SetDoublyUnderlined(true);
VERIFY_IS_TRUE(_pDispatch->SetGraphicsRendition({ rgOptions, cOptions }));
// And then restore...
cOptions = 0;
_testGetSet->_expectedAttribute = {};
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_RED);
_testGetSet->_expectedAttribute.SetIndexedBackground(TextColor::DARK_BLUE);
_testGetSet->_expectedAttribute.SetBold(true);
VERIFY_IS_TRUE(_pDispatch->PopGraphicsRendition());
}
TEST_METHOD(GraphicsPersistBrightnessTests)
{
Log::Comment(L"Starting test...");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
VTParameter rgOptions[16];
size_t cOptions = 1;
Log::Comment(L"Test 1: Basic brightness test");
Log::Comment(L"Resetting graphics options");
rgOptions[0] = DispatchTypes::GraphicsOptions::Off;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
Log::Comment(L"Testing graphics 'Foreground Color Blue'");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundBlue;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
Log::Comment(L"Enabling brightness");
rgOptions[0] = DispatchTypes::GraphicsOptions::BoldBright;
_testGetSet->_expectedAttribute.SetBold(true);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_TRUE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Testing graphics 'Foreground Color Green, with brightness'");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundGreen;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_TRUE(WI_IsFlagSet(_testGetSet->_attribute.GetLegacyAttributes(), FOREGROUND_GREEN));
VERIFY_IS_TRUE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Test 2: Disable brightness, use a bright color, next normal call remains not bright");
Log::Comment(L"Resetting graphics options");
rgOptions[0] = DispatchTypes::GraphicsOptions::Off;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_TRUE(WI_IsFlagClear(_testGetSet->_attribute.GetLegacyAttributes(), FOREGROUND_INTENSITY));
VERIFY_IS_FALSE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Testing graphics 'Foreground Color Bright Blue'");
rgOptions[0] = DispatchTypes::GraphicsOptions::BrightForegroundBlue;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_BLUE);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_FALSE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Testing graphics 'Foreground Color Blue', brightness of 9x series doesn't persist");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundBlue;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_FALSE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Test 3: Enable brightness, use a bright color, brightness persists to next normal call");
Log::Comment(L"Resetting graphics options");
rgOptions[0] = DispatchTypes::GraphicsOptions::Off;
_testGetSet->_expectedAttribute = {};
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_FALSE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Testing graphics 'Foreground Color Blue'");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundBlue;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_FALSE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Enabling brightness");
rgOptions[0] = DispatchTypes::GraphicsOptions::BoldBright;
_testGetSet->_expectedAttribute.SetBold(true);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_TRUE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Testing graphics 'Foreground Color Bright Blue'");
rgOptions[0] = DispatchTypes::GraphicsOptions::BrightForegroundBlue;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::BRIGHT_BLUE);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_TRUE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Testing graphics 'Foreground Color Blue, with brightness', brightness of 9x series doesn't affect brightness");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundBlue;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_BLUE);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_TRUE(_testGetSet->_attribute.IsBold());
Log::Comment(L"Testing graphics 'Foreground Color Green, with brightness'");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundGreen;
_testGetSet->_expectedAttribute.SetIndexedForeground(TextColor::DARK_GREEN);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
VERIFY_IS_TRUE(_testGetSet->_attribute.IsBold());
}
TEST_METHOD(DeviceStatusReportTests)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Verify failure when using bad status.");
_testGetSet->PrepData();
VERIFY_IS_FALSE(_pDispatch.get()->DeviceStatusReport((DispatchTypes::AnsiStatusType)-1));
}
TEST_METHOD(DeviceStatus_OperatingStatusTests)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Verify good operating condition.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch.get()->DeviceStatusReport(DispatchTypes::AnsiStatusType::OS_OperatingStatus));
_testGetSet->ValidateInputEvent(L"\x1b[0n");
}
TEST_METHOD(DeviceStatus_CursorPositionReportTests)
{
Log::Comment(L"Starting test...");
{
Log::Comment(L"Test 1: Verify normal cursor response position.");
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
// start with the cursor position in the buffer.
COORD coordCursorExpected = _testGetSet->_cursorPos;
// to get to VT, we have to adjust it to its position relative to the viewport top.
coordCursorExpected.Y -= _testGetSet->_viewport.Top;
// Then note that VT is 1,1 based for the top left, so add 1. (The rest of the console uses 0,0 for array index bases.)
coordCursorExpected.X++;
coordCursorExpected.Y++;
VERIFY_IS_TRUE(_pDispatch.get()->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
wchar_t pwszBuffer[50];
swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[%d;%dR", coordCursorExpected.Y, coordCursorExpected.X);
_testGetSet->ValidateInputEvent(pwszBuffer);
}
{
Log::Comment(L"Test 2: Verify multiple CPRs with a cursor move between them");
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
// enable retention so that the two DSR responses don't delete eachother
auto retentionScope{ _testGetSet->EnableInputRetentionInScope() };
// start with the cursor position in the buffer.
til::point coordCursorExpectedFirst{ _testGetSet->_cursorPos };
// to get to VT, we have to adjust it to its position relative to the viewport top.
coordCursorExpectedFirst -= til::point{ 0, _testGetSet->_viewport.Top };
// Then note that VT is 1,1 based for the top left, so add 1. (The rest of the console uses 0,0 for array index bases.)
coordCursorExpectedFirst += til::point{ 1, 1 };
VERIFY_IS_TRUE(_pDispatch.get()->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
_testGetSet->_cursorPos.X++;
_testGetSet->_cursorPos.Y++;
auto coordCursorExpectedSecond{ coordCursorExpectedFirst };
coordCursorExpectedSecond += til::point{ 1, 1 };
VERIFY_IS_TRUE(_pDispatch.get()->DeviceStatusReport(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport));
wchar_t pwszBuffer[50];
swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[%d;%dR\x1b[%d;%dR", coordCursorExpectedFirst.y<int>(), coordCursorExpectedFirst.x<int>(), coordCursorExpectedSecond.y<int>(), coordCursorExpectedSecond.x<int>());
_testGetSet->ValidateInputEvent(pwszBuffer);
}
}
TEST_METHOD(DeviceAttributesTests)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Verify normal response.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch.get()->DeviceAttributes());
PCWSTR pwszExpectedResponse = L"\x1b[?1;0c";
_testGetSet->ValidateInputEvent(pwszExpectedResponse);
Log::Comment(L"Test 2: Verify failure when WriteConsoleInput doesn't work.");
_testGetSet->PrepData();
_testGetSet->_privateWriteConsoleInputWResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->DeviceAttributes());
}
TEST_METHOD(SecondaryDeviceAttributesTests)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Verify normal response.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch.get()->SecondaryDeviceAttributes());
PCWSTR pwszExpectedResponse = L"\x1b[>0;10;1c";
_testGetSet->ValidateInputEvent(pwszExpectedResponse);
Log::Comment(L"Test 2: Verify failure when WriteConsoleInput doesn't work.");
_testGetSet->PrepData();
_testGetSet->_privateWriteConsoleInputWResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->SecondaryDeviceAttributes());
}
TEST_METHOD(TertiaryDeviceAttributesTests)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Verify normal response.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch.get()->TertiaryDeviceAttributes());
PCWSTR pwszExpectedResponse = L"\x1bP!|00000000\x1b\\";
_testGetSet->ValidateInputEvent(pwszExpectedResponse);
Log::Comment(L"Test 2: Verify failure when WriteConsoleInput doesn't work.");
_testGetSet->PrepData();
_testGetSet->_privateWriteConsoleInputWResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->TertiaryDeviceAttributes());
}
TEST_METHOD(RequestTerminalParametersTests)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Verify response for unsolicited permission.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited));
_testGetSet->ValidateInputEvent(L"\x1b[2;1;1;128;128;1;0x");
Log::Comment(L"Test 2: Verify response for solicited permission.");
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Solicited));
_testGetSet->ValidateInputEvent(L"\x1b[3;1;1;128;128;1;0x");
Log::Comment(L"Test 3: Verify failure with invalid parameter.");
_testGetSet->PrepData();
VERIFY_IS_FALSE(_pDispatch.get()->RequestTerminalParameters((DispatchTypes::ReportingPermission)2));
Log::Comment(L"Test 4: Verify failure when WriteConsoleInput doesn't work.");
_testGetSet->PrepData();
_testGetSet->_privateWriteConsoleInputWResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->RequestTerminalParameters(DispatchTypes::ReportingPermission::Unsolicited));
}
TEST_METHOD(RequestSettingsTests)
{
const auto requestSetting = [=](const std::wstring_view settingId = {}) {
const auto stringHandler = _pDispatch.get()->RequestSetting();
for (auto ch : settingId)
{
stringHandler(ch);
}
stringHandler(L'\033'); // String terminator
};
Log::Comment(L"Requesting DECSTBM margins (5 to 10).");
_testGetSet->PrepData();
_pDispatch.get()->SetTopBottomScrollingMargins(5, 10);
requestSetting(L"r");
_testGetSet->ValidateInputEvent(L"\033P1$r5;10r\033\\");
Log::Comment(L"Requesting DECSTBM margins (full screen).");
_testGetSet->PrepData();
// Set screen height to 25 - this will be the expected margin range.
_testGetSet->_viewport.Bottom = _testGetSet->_viewport.Top + 25;
_pDispatch.get()->SetTopBottomScrollingMargins(0, 0);
requestSetting(L"r");
_testGetSet->ValidateInputEvent(L"\033P1$r1;25r\033\\");
Log::Comment(L"Requesting SGR attributes (default).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0m\033\\");
Log::Comment(L"Requesting SGR attributes (bold, underlined, reversed).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
_testGetSet->_attribute.SetBold(true);
_testGetSet->_attribute.SetUnderlined(true);
_testGetSet->_attribute.SetReverseVideo(true);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;1;4;7m\033\\");
Log::Comment(L"Requesting SGR attributes (faint, blinking, invisible).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
_testGetSet->_attribute.SetFaint(true);
_testGetSet->_attribute.SetBlinking(true);
_testGetSet->_attribute.SetInvisible(true);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;2;5;8m\033\\");
Log::Comment(L"Requesting SGR attributes (italic, crossed-out).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
_testGetSet->_attribute.SetItalic(true);
_testGetSet->_attribute.SetCrossedOut(true);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;3;9m\033\\");
Log::Comment(L"Requesting SGR attributes (doubly underlined, overlined).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
_testGetSet->_attribute.SetDoublyUnderlined(true);
_testGetSet->_attribute.SetOverlined(true);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;21;53m\033\\");
Log::Comment(L"Requesting SGR attributes (standard colors).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
_testGetSet->_attribute.SetIndexedForeground(TextColor::DARK_YELLOW);
_testGetSet->_attribute.SetIndexedBackground(TextColor::DARK_CYAN);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;33;46m\033\\");
Log::Comment(L"Requesting SGR attributes (AIX colors).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
_testGetSet->_attribute.SetIndexedForeground(TextColor::BRIGHT_CYAN);
_testGetSet->_attribute.SetIndexedBackground(TextColor::BRIGHT_YELLOW);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;96;103m\033\\");
Log::Comment(L"Requesting SGR attributes (ITU indexed colors).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
_testGetSet->_attribute.SetIndexedForeground256(123);
_testGetSet->_attribute.SetIndexedBackground256(45);
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;38;5;123;48;5;45m\033\\");
Log::Comment(L"Requesting SGR attributes (ITU RGB colors).");
_testGetSet->PrepData();
_testGetSet->_attribute = {};
_testGetSet->_attribute.SetForeground(RGB(12, 34, 56));
_testGetSet->_attribute.SetBackground(RGB(65, 43, 21));
requestSetting(L"m");
_testGetSet->ValidateInputEvent(L"\033P1$r0;38;2;12;34;56;48;2;65;43;21m\033\\");
Log::Comment(L"Requesting an unsupported setting.");
_testGetSet->PrepData();
requestSetting(L"x");
_testGetSet->ValidateInputEvent(L"\033P0$r\033\\");
}
TEST_METHOD(CursorKeysModeTest)
{
Log::Comment(L"Starting test...");
// success cases
// set numeric mode = true
Log::Comment(L"Test 1: application mode = false");
_testGetSet->_setInputModeResult = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::CursorKey;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->SetCursorKeysMode(false));
// set numeric mode = false
Log::Comment(L"Test 2: application mode = true");
_testGetSet->_setInputModeResult = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::CursorKey;
_testGetSet->_expectedInputModeEnabled = true;
VERIFY_IS_TRUE(_pDispatch.get()->SetCursorKeysMode(true));
}
TEST_METHOD(KeypadModeTest)
{
Log::Comment(L"Starting test...");
// success cases
// set numeric mode = true
Log::Comment(L"Test 1: application mode = false");
_testGetSet->_setInputModeResult = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::Keypad;
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->SetKeypadMode(false));
// set numeric mode = false
Log::Comment(L"Test 2: application mode = true");
_testGetSet->_setInputModeResult = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::Keypad;
_testGetSet->_expectedInputModeEnabled = true;
VERIFY_IS_TRUE(_pDispatch.get()->SetKeypadMode(true));
}
TEST_METHOD(AnsiModeTest)
{
Log::Comment(L"Starting test...");
// success cases
// set ansi mode = true
Log::Comment(L"Test 1: ansi mode = true");
_testGetSet->_privateSetAnsiModeResult = true;
_testGetSet->_expectedAnsiMode = true;
VERIFY_IS_TRUE(_pDispatch.get()->SetAnsiMode(true));
// set ansi mode = false
Log::Comment(L"Test 2: ansi mode = false.");
_testGetSet->_privateSetAnsiModeResult = true;
_testGetSet->_expectedAnsiMode = false;
VERIFY_IS_TRUE(_pDispatch.get()->SetAnsiMode(false));
}
TEST_METHOD(AllowBlinkingTest)
{
Log::Comment(L"Starting test...");
// success cases
// set numeric mode = true
Log::Comment(L"Test 1: enable blinking = true");
_testGetSet->_privateAllowCursorBlinkingResult = TRUE;
_testGetSet->_enable = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableCursorBlinking(true));
// set numeric mode = false
Log::Comment(L"Test 2: enable blinking = false");
_testGetSet->_privateAllowCursorBlinkingResult = TRUE;
_testGetSet->_enable = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableCursorBlinking(false));
}
TEST_METHOD(ScrollMarginsTest)
{
Log::Comment(L"Starting test...");
SMALL_RECT srTestMargins = { 0 };
_testGetSet->_bufferSize = { 100, 600 };
_testGetSet->_viewport.Right = 8;
_testGetSet->_viewport.Bottom = 8;
_testGetSet->_getConsoleScreenBufferInfoExResult = TRUE;
SHORT sScreenHeight = _testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top;
Log::Comment(L"Test 1: Verify having both values is valid.");
_testGetSet->_SetMarginsHelper(&srTestMargins, 2, 6);
_testGetSet->_privateSetScrollingRegionResult = TRUE;
_testGetSet->_setConsoleCursorPositionResult = true;
_testGetSet->_moveToBottomResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 2: Verify having only top is valid.");
_testGetSet->_SetMarginsHelper(&srTestMargins, 7, 0);
_testGetSet->_expectedScrollRegion.Bottom = _testGetSet->_viewport.Bottom - 1; // We expect the bottom to be the bottom of the viewport, exclusive.
_testGetSet->_privateSetScrollingRegionResult = TRUE;
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 3: Verify having only bottom is valid.");
_testGetSet->_SetMarginsHelper(&srTestMargins, 0, 7);
_testGetSet->_privateSetScrollingRegionResult = TRUE;
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 4: Verify having no values is valid.");
_testGetSet->_SetMarginsHelper(&srTestMargins, 0, 0);
_testGetSet->_privateSetScrollingRegionResult = TRUE;
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 5: Verify having both values, but bad bounds is invalid.");
_testGetSet->_SetMarginsHelper(&srTestMargins, 7, 3);
_testGetSet->_privateSetScrollingRegionResult = TRUE;
VERIFY_IS_FALSE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 6: Verify setting margins to (0, height) clears them");
// First set,
_testGetSet->_privateSetScrollingRegionResult = TRUE;
_testGetSet->_SetMarginsHelper(&srTestMargins, 2, 6);
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
// Then clear
_testGetSet->_SetMarginsHelper(&srTestMargins, 0, sScreenHeight);
_testGetSet->_expectedScrollRegion.Top = 0;
_testGetSet->_expectedScrollRegion.Bottom = 0;
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 7: Verify setting margins to (1, height) clears them");
// First set,
_testGetSet->_privateSetScrollingRegionResult = TRUE;
_testGetSet->_SetMarginsHelper(&srTestMargins, 2, 6);
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
// Then clear
_testGetSet->_SetMarginsHelper(&srTestMargins, 1, sScreenHeight);
_testGetSet->_expectedScrollRegion.Top = 0;
_testGetSet->_expectedScrollRegion.Bottom = 0;
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 8: Verify setting margins to (1, 0) clears them");
// First set,
_testGetSet->_privateSetScrollingRegionResult = TRUE;
_testGetSet->_SetMarginsHelper(&srTestMargins, 2, 6);
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
// Then clear
_testGetSet->_SetMarginsHelper(&srTestMargins, 1, 0);
_testGetSet->_expectedScrollRegion.Top = 0;
_testGetSet->_expectedScrollRegion.Bottom = 0;
VERIFY_IS_TRUE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 9: Verify having top and bottom margin the same is invalid.");
_testGetSet->_SetMarginsHelper(&srTestMargins, 4, 4);
_testGetSet->_privateSetScrollingRegionResult = TRUE;
VERIFY_IS_FALSE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 10: Verify having top margin out of bounds is invalid.");
_testGetSet->_SetMarginsHelper(&srTestMargins, sScreenHeight + 1, sScreenHeight + 10);
_testGetSet->_privateSetScrollingRegionResult = TRUE;
VERIFY_IS_FALSE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
Log::Comment(L"Test 11: Verify having bottom margin out of bounds is invalid.");
_testGetSet->_SetMarginsHelper(&srTestMargins, 1, sScreenHeight + 1);
_testGetSet->_privateSetScrollingRegionResult = TRUE;
VERIFY_IS_FALSE(_pDispatch.get()->SetTopBottomScrollingMargins(srTestMargins.Top, srTestMargins.Bottom));
}
TEST_METHOD(LineFeedTest)
{
Log::Comment(L"Starting test...");
// All test cases need the LineFeed call to succeed.
_testGetSet->_privateLineFeedResult = TRUE;
Log::Comment(L"Test 1: Line feed without carriage return.");
_testGetSet->_expectedLineFeedWithReturn = false;
VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::WithoutReturn));
Log::Comment(L"Test 2: Line feed with carriage return.");
_testGetSet->_expectedLineFeedWithReturn = true;
VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::WithReturn));
Log::Comment(L"Test 3: Line feed depends on mode, and mode reset.");
_testGetSet->_privateGetLineFeedModeResult = false;
_testGetSet->_expectedLineFeedWithReturn = false;
VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
Log::Comment(L"Test 4: Line feed depends on mode, and mode set.");
_testGetSet->_privateGetLineFeedModeResult = true;
_testGetSet->_expectedLineFeedWithReturn = true;
VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
}
TEST_METHOD(SetConsoleTitleTest)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: set title to be non-null");
_testGetSet->_setConsoleTitleWResult = TRUE;
_testGetSet->_expectedWindowTitle = L"Foo bar";
VERIFY_IS_TRUE(_pDispatch.get()->SetWindowTitle(_testGetSet->_expectedWindowTitle));
Log::Comment(L"Test 2: set title to be null");
_testGetSet->_setConsoleTitleWResult = FALSE;
_testGetSet->_expectedWindowTitle = {};
VERIFY_IS_TRUE(_pDispatch.get()->SetWindowTitle({}));
}
TEST_METHOD(TestMouseModes)
{
Log::Comment(L"Starting test...");
Log::Comment(L"Test 1: Test Default Mouse Mode");
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::DefaultMouseTracking;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableVT200MouseMode(true));
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableVT200MouseMode(false));
Log::Comment(L"Test 2: Test UTF-8 Extended Mouse Mode");
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::Utf8MouseEncoding;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableUTF8ExtendedMouseMode(true));
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableUTF8ExtendedMouseMode(false));
Log::Comment(L"Test 3: Test SGR Extended Mouse Mode");
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::SgrMouseEncoding;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableSGRExtendedMouseMode(true));
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableSGRExtendedMouseMode(false));
Log::Comment(L"Test 4: Test Button-Event Mouse Mode");
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::ButtonEventMouseTracking;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableButtonEventMouseMode(true));
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableButtonEventMouseMode(false));
Log::Comment(L"Test 5: Test Any-Event Mouse Mode");
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::AnyEventMouseTracking;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableAnyEventMouseMode(true));
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableAnyEventMouseMode(false));
Log::Comment(L"Test 6: Test Alt Scroll Mouse Mode");
_testGetSet->_expectedInputModeEnabled = true;
_testGetSet->_expectedInputMode = TerminalInput::Mode::AlternateScroll;
_testGetSet->_setInputModeResult = true;
VERIFY_IS_TRUE(_pDispatch.get()->EnableAlternateScroll(true));
_testGetSet->_expectedInputModeEnabled = false;
VERIFY_IS_TRUE(_pDispatch.get()->EnableAlternateScroll(false));
}
TEST_METHOD(Xterm256ColorTest)
{
Log::Comment(L"Starting test...");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
VTParameter rgOptions[16];
size_t cOptions = 3;
_testGetSet->_privateGetColorTableEntryResult = true;
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
Log::Comment(L"Test 1: Change Foreground");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgOptions[2] = (DispatchTypes::GraphicsOptions)2; // Green
_testGetSet->_expectedAttribute.SetIndexedForeground256(TextColor::DARK_GREEN);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
Log::Comment(L"Test 2: Change Background");
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgOptions[2] = (DispatchTypes::GraphicsOptions)9; // Bright Red
_testGetSet->_expectedAttribute.SetIndexedBackground256(TextColor::BRIGHT_RED);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
Log::Comment(L"Test 3: Change Foreground to RGB color");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgOptions[2] = (DispatchTypes::GraphicsOptions)42; // Arbitrary Color
_testGetSet->_expectedAttribute.SetIndexedForeground256(42);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
Log::Comment(L"Test 4: Change Background to RGB color");
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgOptions[2] = (DispatchTypes::GraphicsOptions)142; // Arbitrary Color
_testGetSet->_expectedAttribute.SetIndexedBackground256(142);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
Log::Comment(L"Test 5: Change Foreground to Legacy Attr while BG is RGB color");
// Unfortunately this test isn't all that good, because the adapterTest adapter isn't smart enough
// to have its own color table and translate the pre-existing RGB BG into a legacy BG.
// Fortunately, the ft_api:RgbColorTests IS smart enough to test that.
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgOptions[2] = (DispatchTypes::GraphicsOptions)9; // Bright Red
_testGetSet->_expectedAttribute.SetIndexedForeground256(TextColor::BRIGHT_RED);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, cOptions }));
}
TEST_METHOD(XtermExtendedColorDefaultParameterTest)
{
Log::Comment(L"Starting test...");
_testGetSet->PrepData(); // default color from here is gray on black, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
VTParameter rgOptions[16];
_testGetSet->_privateGetColorTableEntryResult = true;
_testGetSet->_expectedAttribute = _testGetSet->_attribute;
Log::Comment(L"Test 1: Change Indexed Foreground with missing index parameter");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
_testGetSet->_expectedAttribute.SetIndexedForeground256(TextColor::DARK_BLACK);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 2 }));
Log::Comment(L"Test 2: Change Indexed Background with default index parameter");
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::BlinkOrXterm256Index;
rgOptions[2] = {};
_testGetSet->_expectedAttribute.SetIndexedBackground256(TextColor::DARK_BLACK);
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 3 }));
Log::Comment(L"Test 3: Change RGB Foreground with all RGB parameters missing");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
_testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 0));
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 2 }));
Log::Comment(L"Test 4: Change RGB Background with some missing RGB parameters");
rgOptions[0] = DispatchTypes::GraphicsOptions::BackgroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgOptions[2] = 123;
_testGetSet->_expectedAttribute.SetBackground(RGB(123, 0, 0));
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 3 }));
Log::Comment(L"Test 5: Change RGB Foreground with some default RGB parameters");
rgOptions[0] = DispatchTypes::GraphicsOptions::ForegroundExtended;
rgOptions[1] = DispatchTypes::GraphicsOptions::RGBColorOrFaint;
rgOptions[2] = {};
rgOptions[3] = {};
rgOptions[4] = 123;
_testGetSet->_expectedAttribute.SetForeground(RGB(0, 0, 123));
VERIFY_IS_TRUE(_pDispatch.get()->SetGraphicsRendition({ rgOptions, 5 }));
}
TEST_METHOD(SetColorTableValue)
{
_testGetSet->PrepData();
_testGetSet->_privateSetColorTableEntryResult = true;
const auto testColor = RGB(1, 2, 3);
_testGetSet->_expectedColorValue = testColor;
for (size_t i = 0; i < 256; i++)
{
_testGetSet->_expectedColorTableIndex = i;
VERIFY_IS_TRUE(_pDispatch.get()->SetColorTableEntry(i, testColor));
}
// Test in pty mode - we should fail, but PrivateSetColorTableEntry should still be called
_testGetSet->_isPty = true;
_testGetSet->_expectedColorTableIndex = 15; // Windows BRIGHT_WHITE
VERIFY_IS_FALSE(_pDispatch.get()->SetColorTableEntry(15, testColor));
}
TEST_METHOD(SoftFontSizeDetection)
{
using CellMatrix = DispatchTypes::DrcsCellMatrix;
using FontSet = DispatchTypes::DrcsFontSet;
using FontUsage = DispatchTypes::DrcsFontUsage;
const auto decdld = [=](const auto cmw, const auto cmh, const auto ss, const auto u, const std::wstring_view data = {}) {
const auto ec = DispatchTypes::DrcsEraseControl::AllChars;
const auto css = DispatchTypes::DrcsCharsetSize::Size94;
const auto cellMatrix = static_cast<DispatchTypes::DrcsCellMatrix>(cmw);
const auto stringHandler = _pDispatch.get()->DownloadDRCS(0, 0, ec, cellMatrix, ss, u, cmh, css);
if (stringHandler)
{
stringHandler(L'B'); // Charset identifier
for (auto ch : data)
{
stringHandler(ch);
}
stringHandler(L'\033'); // String terminator
}
return stringHandler != nullptr;
};
// Matrix sizes at 80x24 should always use a 10x10 cell size (VT2xx).
Log::Comment(L"Matrix 5x10 for 80x24 font set with text usage");
_testGetSet->_expectedCellSize = { 10, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Size5x10, 0, FontSet::Size80x24, FontUsage::Text));
Log::Comment(L"Matrix 6x10 for 80x24 font set with text usage");
_testGetSet->_expectedCellSize = { 10, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Size6x10, 0, FontSet::Size80x24, FontUsage::Text));
Log::Comment(L"Matrix 7x10 for 80x24 font set with text usage");
_testGetSet->_expectedCellSize = { 10, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Size7x10, 0, FontSet::Size80x24, FontUsage::Text));
// At 132x24 the cell size is typically 6x10 (VT240), but could be 10x10 (VT220)
Log::Comment(L"Matrix 5x10 for 132x24 font set with text usage");
_testGetSet->_expectedCellSize = { 6, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Size5x10, 0, FontSet::Size132x24, FontUsage::Text));
Log::Comment(L"Matrix 6x10 for 132x24 font set with text usage");
_testGetSet->_expectedCellSize = { 6, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Size6x10, 0, FontSet::Size132x24, FontUsage::Text));
Log::Comment(L"Matrix 7x10 for 132x24 font set with text usage (VT220 only)");
_testGetSet->_expectedCellSize = { 10, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Size7x10, 0, FontSet::Size132x24, FontUsage::Text));
// Full cell usage is invalid for all matrix sizes except 6x10 at 132x24.
Log::Comment(L"Matrix 5x10 for 80x24 font set with full cell usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Size5x10, 0, FontSet::Size80x24, FontUsage::FullCell));
Log::Comment(L"Matrix 6x10 for 80x24 font set with full cell usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Size6x10, 0, FontSet::Size80x24, FontUsage::FullCell));
Log::Comment(L"Matrix 7x10 for 80x24 font set with full cell usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Size7x10, 0, FontSet::Size80x24, FontUsage::FullCell));
Log::Comment(L"Matrix 5x10 for 132x24 font set with full cell usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Size5x10, 0, FontSet::Size132x24, FontUsage::FullCell));
Log::Comment(L"Matrix 6x10 for 132x24 font set with full cell usage");
_testGetSet->_expectedCellSize = { 6, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Size6x10, 0, FontSet::Size132x24, FontUsage::FullCell));
Log::Comment(L"Matrix 7x10 for 132x24 font set with full cell usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Size7x10, 0, FontSet::Size132x24, FontUsage::FullCell));
// Matrix size 1 is always invalid.
Log::Comment(L"Matrix 1 for 80x24 font set with text usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Invalid, 0, FontSet::Size80x24, FontUsage::Text));
Log::Comment(L"Matrix 1 for 132x24 font set with text usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Invalid, 0, FontSet::Size132x24, FontUsage::Text));
Log::Comment(L"Matrix 1 for 80x24 font set with full cell usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Invalid, 0, FontSet::Size80x24, FontUsage::FullCell));
Log::Comment(L"Matrix 1 for 132x24 font set with full cell usage (invalid)");
VERIFY_IS_FALSE(decdld(CellMatrix::Invalid, 0, FontSet::Size132x24, FontUsage::FullCell));
// The height parameter has no effect when a matrix size is used.
Log::Comment(L"Matrix 7x10 with unused height parameter");
_testGetSet->_expectedCellSize = { 10, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Size7x10, 20, FontSet::Size80x24, FontUsage::Text));
// Full cell fonts with explicit dimensions are accepted as their given cell size.
Log::Comment(L"Explicit 13x17 for 80x24 font set with full cell usage");
_testGetSet->_expectedCellSize = { 13, 17 };
VERIFY_IS_TRUE(decdld(13, 17, FontSet::Size80x24, FontUsage::FullCell));
Log::Comment(L"Explicit 9x25 for 132x24 font set with full cell usage");
_testGetSet->_expectedCellSize = { 9, 25 };
VERIFY_IS_TRUE(decdld(9, 25, FontSet::Size132x24, FontUsage::FullCell));
// Cell sizes outside the maximum supported range (16x32) are invalid.
Log::Comment(L"Explicit 18x38 for 80x24 font set with full cell usage (invalid)");
VERIFY_IS_FALSE(decdld(18, 38, FontSet::Size80x24, FontUsage::FullCell));
// Text fonts with explicit dimensions are interpreted as their closest matching device.
Log::Comment(L"Explicit 12x12 for 80x24 font set with text usage (VT320)");
_testGetSet->_expectedCellSize = { 15, 12 };
VERIFY_IS_TRUE(decdld(12, 12, FontSet::Size80x24, FontUsage::Text));
Log::Comment(L"Explicit 9x20 for 80x24 font set with text usage (VT340)");
_testGetSet->_expectedCellSize = { 10, 20 };
VERIFY_IS_TRUE(decdld(9, 20, FontSet::Size80x24, FontUsage::Text));
Log::Comment(L"Explicit 10x30 for 80x24 font set with text usage (VT382)");
_testGetSet->_expectedCellSize = { 12, 30 };
VERIFY_IS_TRUE(decdld(10, 30, FontSet::Size80x24, FontUsage::Text));
Log::Comment(L"Explicit 8x16 for 80x24 font set with text usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 10, 16 };
VERIFY_IS_TRUE(decdld(8, 16, FontSet::Size80x24, FontUsage::Text));
Log::Comment(L"Explicit 7x12 for 132x24 font set with text usage (VT320)");
_testGetSet->_expectedCellSize = { 9, 12 };
VERIFY_IS_TRUE(decdld(7, 12, FontSet::Size132x24, FontUsage::Text));
Log::Comment(L"Explicit 5x20 for 132x24 font set with text usage (VT340)");
_testGetSet->_expectedCellSize = { 6, 20 };
VERIFY_IS_TRUE(decdld(5, 20, FontSet::Size132x24, FontUsage::Text));
Log::Comment(L"Explicit 6x30 for 132x24 font set with text usage (VT382)");
_testGetSet->_expectedCellSize = { 7, 30 };
VERIFY_IS_TRUE(decdld(6, 30, FontSet::Size132x24, FontUsage::Text));
Log::Comment(L"Explicit 5x16 for 132x24 font set with text usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 6, 16 };
VERIFY_IS_TRUE(decdld(5, 16, FontSet::Size132x24, FontUsage::Text));
// Font sets with more than 24 lines must be VT420/VT5xx.
Log::Comment(L"80x36 font set with text usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 10, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x36, FontUsage::Text));
Log::Comment(L"80x48 font set with text usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 10, 8 };
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x48, FontUsage::Text));
Log::Comment(L"132x36 font set with text usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 6, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x36, FontUsage::Text));
Log::Comment(L"132x48 font set with text usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 6, 8 };
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x48, FontUsage::Text));
Log::Comment(L"80x36 font set with full cell usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 10, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x36, FontUsage::FullCell));
Log::Comment(L"80x48 font set with full cell usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 10, 8 };
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x48, FontUsage::FullCell));
Log::Comment(L"132x36 font set with full cell usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 6, 10 };
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x36, FontUsage::FullCell));
Log::Comment(L"132x48 font set with full cell usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 6, 8 };
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x48, FontUsage::FullCell));
// Without an explicit size, the cell size is estimated from the number of sixels
// used in the character bitmaps. But note that sixel heights are always a multiple
// of 6, so will often be larger than the cell size for which they were intended.
Log::Comment(L"8x12 bitmap for 80x24 font set with text usage (VT2xx)");
_testGetSet->_expectedCellSize = { 10, 10 };
const auto bitmapOf8x12 = L"????????/????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf8x12));
Log::Comment(L"12x12 bitmap for 80x24 font set with text usage (VT320)");
_testGetSet->_expectedCellSize = { 15, 12 };
const auto bitmapOf12x12 = L"????????????/????????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf12x12));
Log::Comment(L"9x24 bitmap for 80x24 font set with text usage (VT340)");
_testGetSet->_expectedCellSize = { 10, 20 };
const auto bitmapOf9x24 = L"?????????/?????????/?????????/?????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf9x24));
Log::Comment(L"10x30 bitmap for 80x24 font set with text usage (VT382)");
_testGetSet->_expectedCellSize = { 12, 30 };
const auto bitmapOf10x30 = L"??????????/??????????/??????????/??????????/??????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf10x30));
Log::Comment(L"8x18 bitmap for 80x24 font set with text usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 10, 16 };
const auto bitmapOf8x18 = L"????????/????????/????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::Text, bitmapOf8x18));
Log::Comment(L"5x12 bitmap for 132x24 font set with text usage (VT240)");
_testGetSet->_expectedCellSize = { 6, 10 };
const auto bitmapOf5x12 = L"?????/?????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf5x12));
Log::Comment(L"7x12 bitmap for 132x24 font set with text usage (VT320)");
_testGetSet->_expectedCellSize = { 9, 12 };
const auto bitmapOf7x12 = L"???????/???????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf7x12));
Log::Comment(L"5x24 bitmap for 132x24 font set with text usage (VT340)");
_testGetSet->_expectedCellSize = { 6, 20 };
const auto bitmapOf5x24 = L"?????/?????/?????/?????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf5x24));
Log::Comment(L"6x30 bitmap for 132x24 font set with text usage (VT382)");
_testGetSet->_expectedCellSize = { 7, 30 };
const auto bitmapOf6x30 = L"??????/??????/??????/??????/??????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf6x30));
Log::Comment(L"5x18 bitmap for 132x24 font set with text usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 6, 16 };
const auto bitmapOf5x18 = L"?????/?????/?????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::Text, bitmapOf5x18));
Log::Comment(L"15x12 bitmap for 80x24 font set with full cell usage (VT320)");
_testGetSet->_expectedCellSize = { 15, 12 };
const auto bitmapOf15x12 = L"???????????????/???????????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::FullCell, bitmapOf15x12));
Log::Comment(L"10x24 bitmap for 80x24 font set with full cell usage (VT340)");
_testGetSet->_expectedCellSize = { 10, 20 };
const auto bitmapOf10x24 = L"??????????/??????????/??????????/??????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::FullCell, bitmapOf10x24));
Log::Comment(L"12x30 bitmap for 80x24 font set with full cell usage (VT382)");
_testGetSet->_expectedCellSize = { 12, 30 };
const auto bitmapOf12x30 = L"????????????/????????????/????????????/????????????/????????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::FullCell, bitmapOf12x30));
Log::Comment(L"10x18 bitmap for 80x24 font set with full cell usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 10, 16 };
const auto bitmapOf10x18 = L"??????????/??????????/??????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size80x24, FontUsage::FullCell, bitmapOf10x18));
Log::Comment(L"6x12 bitmap for 132x24 font set with full cell usage (VT240)");
_testGetSet->_expectedCellSize = { 6, 10 };
const auto bitmapOf6x12 = L"??????/??????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf6x12));
Log::Comment(L"9x12 bitmap for 132x24 font set with full cell usage (VT320)");
_testGetSet->_expectedCellSize = { 9, 12 };
const auto bitmapOf9x12 = L"?????????/?????????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf9x12));
Log::Comment(L"6x24 bitmap for 132x24 font set with full cell usage (VT340)");
_testGetSet->_expectedCellSize = { 6, 20 };
const auto bitmapOf6x24 = L"??????/??????/??????/??????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf6x24));
Log::Comment(L"7x30 bitmap for 132x24 font set with full cell usage (VT382)");
_testGetSet->_expectedCellSize = { 7, 30 };
const auto bitmapOf7x30 = L"???????/???????/???????/???????/???????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf7x30));
Log::Comment(L"6x18 bitmap for 132x24 font set with full cell usage (VT420/VT5xx)");
_testGetSet->_expectedCellSize = { 6, 16 };
const auto bitmapOf6x18 = L"??????/??????/??????";
VERIFY_IS_TRUE(decdld(CellMatrix::Default, 0, FontSet::Size132x24, FontUsage::FullCell, bitmapOf6x18));
}
private:
TestGetSet* _testGetSet; // non-ownership pointer
std::unique_ptr<AdaptDispatch> _pDispatch;
};