terminal/src/buffer/out/ut_textbuffer/TextAttributeTests.cpp
James Holderness bb71179a24
Consolidate the color palette APIs (#11784)
This PR merges the default colors and cursor color into the main color
table, enabling us to simplify the `ConGetSet` and `ITerminalApi`
interfaces, with just two methods required for getting and setting any
form of color palette entry.

The is a follow-up to the color table standardization in #11602, and a
another small step towards de-duplicating `AdaptDispatch` and
`TerminalDispatch` for issue #3849. It should also make it easier to
support color queries (#3718) and a configurable bold color (#5682) in
the future.

On the conhost side, default colors could originally be either indexed
positions in the 16-color table, or separate standalone RGB values. With
the new system, the default colors will always be in the color table, so
we just need to track their index positions.

To make this work, those positions need to be calculated at startup
based on the loaded registry/shortcut settings, and updated when
settings are changed (this is handled in
`CalculateDefaultColorIndices`). But the plus side is that it's now much
easier to lookup the default color values for rendering.

For now the default colors in Windows Terminal use hardcoded positions,
because it doesn't need indexed default colors like conhost. But in the
future I'd like to extend the index handling to both terminals, so we
can eventually support the VT525 indexed color operations.

As for the cursor color, that was previously stored in the `Cursor`
class, which meant that it needed to be copied around in various places
where cursors were being instantiated. Now that it's managed separately
in the color table, a lot of that code is no longer required.

## Validation
Some of the unit test initialization code needed to be updated to setup
the color table and default index values as required for the new system.
There were also some adjustments needed to account for API changes, in
particular for methods that now take index values for the default colors
in place of COLORREFs. But for the most part, the essential behavior of
the tests remains unchanged.

I've also run a variety of manual tests looking at the legacy console
APIs as well as the various VT color sequences, and checking that
everything works as expected when color schemes are changed, both in
Windows Terminal and conhost, and in the latter case with both indexed
colors and RGB values.

Closes #11768
2021-11-23 18:28:55 +00:00

324 lines
15 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
#include "../../inc/consoletaeftemplates.hpp"
#include "../TextAttribute.hpp"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class TextAttributeTests
{
TEST_CLASS(TextAttributeTests);
TEST_CLASS_SETUP(ClassSetup);
TEST_METHOD(TestRoundtripLegacy);
TEST_METHOD(TestRoundtripMetaBits);
TEST_METHOD(TestRoundtripExhaustive);
TEST_METHOD(TestTextAttributeColorGetters);
TEST_METHOD(TestReverseDefaultColors);
TEST_METHOD(TestRoundtripDefaultColors);
TEST_METHOD(TestBoldAsBright);
std::array<COLORREF, TextColor::TABLE_SIZE> _colorTable;
const COLORREF _defaultFg = RGB(1, 2, 3);
const COLORREF _defaultBg = RGB(4, 5, 6);
const size_t _defaultFgIndex = TextColor::DEFAULT_FOREGROUND;
const size_t _defaultBgIndex = TextColor::DEFAULT_BACKGROUND;
};
bool TextAttributeTests::ClassSetup()
{
_colorTable[0] = RGB(12, 12, 12); // Black
_colorTable[1] = RGB(0, 55, 218); // Dark Blue
_colorTable[2] = RGB(19, 161, 14); // Dark Green
_colorTable[3] = RGB(58, 150, 221); // Dark Cyan
_colorTable[4] = RGB(197, 15, 31); // Dark Red
_colorTable[5] = RGB(136, 23, 152); // Dark Magenta
_colorTable[6] = RGB(193, 156, 0); // Dark Yellow
_colorTable[7] = RGB(204, 204, 204); // Dark White
_colorTable[8] = RGB(118, 118, 118); // Bright Black
_colorTable[9] = RGB(59, 120, 255); // Bright Blue
_colorTable[10] = RGB(22, 198, 12); // Bright Green
_colorTable[11] = RGB(97, 214, 214); // Bright Cyan
_colorTable[12] = RGB(231, 72, 86); // Bright Red
_colorTable[13] = RGB(180, 0, 158); // Bright Magenta
_colorTable[14] = RGB(249, 241, 165); // Bright Yellow
_colorTable[15] = RGB(242, 242, 242); // White
_colorTable[_defaultFgIndex] = _defaultFg;
_colorTable[_defaultBgIndex] = _defaultBg;
return true;
}
void TextAttributeTests::TestRoundtripLegacy()
{
WORD expectedLegacy = FOREGROUND_BLUE | BACKGROUND_RED;
WORD bgOnly = expectedLegacy & BG_ATTRS;
WORD bgShifted = bgOnly >> 4;
BYTE bgByte = (BYTE)(bgShifted);
VERIFY_ARE_EQUAL(FOREGROUND_RED, bgByte);
auto attr = TextAttribute(expectedLegacy);
VERIFY_IS_TRUE(attr.IsLegacy());
VERIFY_ARE_EQUAL(expectedLegacy, attr.GetLegacyAttributes());
}
void TextAttributeTests::TestRoundtripMetaBits()
{
WORD metaFlags[] = {
COMMON_LVB_GRID_HORIZONTAL,
COMMON_LVB_GRID_LVERTICAL,
COMMON_LVB_GRID_RVERTICAL,
COMMON_LVB_REVERSE_VIDEO,
COMMON_LVB_UNDERSCORE
};
for (int i = 0; i < ARRAYSIZE(metaFlags); ++i)
{
WORD flag = metaFlags[i];
WORD expectedLegacy = FOREGROUND_BLUE | BACKGROUND_RED | flag;
WORD metaOnly = expectedLegacy & META_ATTRS;
VERIFY_ARE_EQUAL(flag, metaOnly);
auto attr = TextAttribute(expectedLegacy);
VERIFY_IS_TRUE(attr.IsLegacy());
VERIFY_ARE_EQUAL(expectedLegacy, attr.GetLegacyAttributes());
VERIFY_ARE_EQUAL(flag, attr._wAttrLegacy);
}
}
void TextAttributeTests::TestRoundtripExhaustive()
{
WORD allAttrs = (META_ATTRS | FG_ATTRS | BG_ATTRS);
// This test covers some 0xdfff test cases, printing out Verify: IsTrue for
// each takes a lot longer than checking.
// Only VERIFY if the comparison actually fails to speed up the test.
Log::Comment(L"This test will check each possible legacy attribute to make "
"sure it roundtrips through the creation of a text attribute.");
Log::Comment(L"It will only log if it fails.");
for (WORD wLegacy = 0; wLegacy < allAttrs; wLegacy++)
{
// 0x2000 is not an actual meta attribute
// COMMON_LVB_TRAILING_BYTE and COMMON_LVB_TRAILING_BYTE are no longer
// stored in the TextAttributes, they're stored in the CharRow
if (WI_IsFlagSet(wLegacy, 0x2000) ||
WI_IsFlagSet(wLegacy, COMMON_LVB_LEADING_BYTE) ||
WI_IsFlagSet(wLegacy, COMMON_LVB_TRAILING_BYTE))
{
continue;
}
auto attr = TextAttribute(wLegacy);
if (wLegacy != attr.GetLegacyAttributes())
{
Log::Comment(NoThrowString().Format(
L"Failed on wLegacy=0x%x", wLegacy));
VERIFY_ARE_EQUAL(wLegacy, attr.GetLegacyAttributes());
}
}
}
void TextAttributeTests::TestTextAttributeColorGetters()
{
const COLORREF red = RGB(255, 0, 0);
const COLORREF faintRed = RGB(127, 0, 0);
const COLORREF green = RGB(0, 255, 0);
TextAttribute attr(red, green);
// verify that calculated foreground/background are the same as the direct
// values when reverse video is not set
VERIFY_IS_FALSE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(red, green), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
// with reverse video set, calculated foreground/background values should be
// switched while getters stay the same
attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(green, red), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
// reset the reverse video
attr.SetReverseVideo(false);
// with faint set, the calculated foreground value should be fainter
// while the background and getters stay the same
attr.SetFaint(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(faintRed, green), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
// with reverse video set, calculated foreground/background values should be
// switched, and the background fainter, while getters stay the same
attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(green, faintRed), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
// reset the reverse video and faint attributes
attr.SetReverseVideo(false);
attr.SetFaint(false);
// with invisible set, the calculated foreground value should match the
// background, while getters stay the same
attr.SetInvisible(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(green, green), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
// with reverse video set, the calculated background value should match
// the foreground, while getters stay the same
attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(red, red), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
}
void TextAttributeTests::TestReverseDefaultColors()
{
const COLORREF red = RGB(255, 0, 0);
const COLORREF green = RGB(0, 255, 0);
TextAttribute attr{};
// verify that calculated foreground/background are the same as the direct
// values when reverse video is not set
VERIFY_IS_FALSE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
// with reverse video set, calculated foreground/background values should be
// switched while getters stay the same
attr.SetReverseVideo(true);
VERIFY_IS_TRUE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, _defaultFg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
attr.SetForeground(red);
VERIFY_IS_TRUE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, red), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
attr.Invert();
VERIFY_IS_FALSE(attr.IsReverseVideo());
attr.SetDefaultForeground();
attr.SetBackground(green);
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, green), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex));
}
void TextAttributeTests::TestRoundtripDefaultColors()
{
// Set the legacy default colors to yellow on blue.
const BYTE fgLegacyDefault = FOREGROUND_RED;
const BYTE bgLegacyDefault = BACKGROUND_BLUE;
TextAttribute::SetLegacyDefaultAttributes(fgLegacyDefault | bgLegacyDefault);
WORD legacyAttribute;
TextAttribute textAttribute;
Log::Comment(L"Foreground legacy default index should map to default text color.");
legacyAttribute = fgLegacyDefault | BACKGROUND_GREEN;
textAttribute.SetDefaultForeground();
textAttribute.SetIndexedBackground256(TextColor::DARK_GREEN);
VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute });
Log::Comment(L"Default foreground text color should map back to legacy default index.");
VERIFY_ARE_EQUAL(legacyAttribute, textAttribute.GetLegacyAttributes());
Log::Comment(L"Background legacy default index should map to default text color.");
legacyAttribute = FOREGROUND_GREEN | bgLegacyDefault;
textAttribute.SetIndexedForeground256(TextColor::DARK_GREEN);
textAttribute.SetDefaultBackground();
VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute });
Log::Comment(L"Default background text color should map back to legacy default index.");
VERIFY_ARE_EQUAL(legacyAttribute, textAttribute.GetLegacyAttributes());
Log::Comment(L"Foreground and background legacy defaults should map to default text colors.");
legacyAttribute = fgLegacyDefault | bgLegacyDefault;
textAttribute.SetDefaultForeground();
textAttribute.SetDefaultBackground();
VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute });
Log::Comment(L"Default foreground and background text colors should map back to legacy defaults.");
VERIFY_ARE_EQUAL(legacyAttribute, textAttribute.GetLegacyAttributes());
// Reset the legacy default colors to white on black.
TextAttribute::SetLegacyDefaultAttributes(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
void TextAttributeTests::TestBoldAsBright()
{
const COLORREF darkBlack = til::at(_colorTable, 0);
const COLORREF brightBlack = til::at(_colorTable, 8);
const COLORREF darkGreen = til::at(_colorTable, 2);
TextAttribute attr{};
// verify that calculated foreground/background are the same as the direct
// values when not bold
VERIFY_IS_FALSE(attr.IsBold());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFgIndex));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBgIndex));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
// with bold set, calculated foreground/background values shouldn't change for the default colors.
attr.SetBold(true);
VERIFY_IS_TRUE(attr.IsBold());
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
attr.SetIndexedForeground(TextColor::DARK_BLACK);
VERIFY_IS_TRUE(attr.IsBold());
Log::Comment(L"Foreground should be bright black when bold is bright is enabled");
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
Log::Comment(L"Foreground should be dark black when bold is bright is disabled");
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
attr.SetIndexedBackground(TextColor::DARK_GREEN);
VERIFY_IS_TRUE(attr.IsBold());
Log::Comment(L"background should be unaffected by 'bold is bright'");
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
attr.SetBold(false);
VERIFY_IS_FALSE(attr.IsBold());
Log::Comment(L"when not bold, 'bold is bright' changes nothing");
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
Log::Comment(L"When set to a bright color, and bold, 'bold is bright' changes nothing");
attr.SetBold(true);
attr.SetIndexedForeground(TextColor::BRIGHT_BLACK);
VERIFY_IS_TRUE(attr.IsBold());
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFgIndex, _defaultBgIndex, false, false, false));
}