diff options
| author | Jeremy Rifkin | 2024-05-04 16:43:52 -0500 |
|---|---|---|
| committer | GitHub | 2024-05-04 23:43:52 +0200 |
| commit | 8ce2426e53b2596a9823b4a314f72f1c3b30be82 (patch) | |
| tree | e8262448f9810687436a3e4cb39372691e7baa31 /tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp | |
| parent | fa5a53df17303339eff8fda5cae0b601af207085 (diff) | |
| download | catch3-8ce2426e53b2596a9823b4a314f72f1c3b30be82.tar.gz | |
Handle ANSI escape sequences when performing column wrapping (#2849)
This PR adds functionality to skip around ANSI escape sequences in catch_textflow so they do not contribute to line length and line wrapping code does not split escape sequences in the middle. I've implemented this by creating a AnsiSkippingString abstraction that has a bidirectional iterator that can skip around escape sequences while iterating. Additionally I refactored Column::const_iterator to be iterator-based rather than index-based so this abstraction is a simple drop-in for std::string.
Currently only color sequences are handled, other escape sequences are left unaffected.
Motivation: Text with ANSI color sequences gets messed up when being output by Catch2 #2833.
Diffstat (limited to 'tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp')
| -rw-r--r-- | tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp b/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp index 653f65ba..de03ed09 100644 --- a/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp @@ -12,6 +12,7 @@ #include <sstream> using Catch::TextFlow::Column; +using Catch::TextFlow::AnsiSkippingString; namespace { static std::string as_written(Column const& c) { @@ -198,3 +199,202 @@ TEST_CASE( "#1400 - TextFlow::Column wrapping would sometimes duplicate words", " in \n" " convallis posuere, libero nisi ultricies orci, nec lobortis."); } + +TEST_CASE( "TextFlow::AnsiSkippingString skips ansi sequences", + "[TextFlow][ansiskippingstring][approvals]" ) { + + SECTION("basic string") { + std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me"; + AnsiSkippingString str(text); + + SECTION( "iterates forward" ) { + auto it = str.begin(); + CHECK(*it == 'a'); + ++it; + CHECK(*it == 'b'); + ++it; + CHECK(*it == 'c'); + ++it; + CHECK(*it == 'd'); + ++it; + CHECK(*it == 'e'); + ++it; + CHECK(it == str.end()); + } + SECTION( "iterates backwards" ) { + auto it = str.end(); + --it; + CHECK(*it == 'e'); + --it; + CHECK(*it == 'd'); + --it; + CHECK(*it == 'c'); + --it; + CHECK(*it == 'b'); + --it; + CHECK(*it == 'a'); + CHECK(it == str.begin()); + } + } + + SECTION( "ansi escape sequences at the start" ) { + std::string text = "\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38mc\033[0md\033[me"; + AnsiSkippingString str(text); + auto it = str.begin(); + CHECK(*it == 'a'); + ++it; + CHECK(*it == 'b'); + ++it; + CHECK(*it == 'c'); + ++it; + CHECK(*it == 'd'); + ++it; + CHECK(*it == 'e'); + ++it; + CHECK(it == str.end()); + --it; + CHECK(*it == 'e'); + --it; + CHECK(*it == 'd'); + --it; + CHECK(*it == 'c'); + --it; + CHECK(*it == 'b'); + --it; + CHECK(*it == 'a'); + CHECK(it == str.begin()); + } + + SECTION( "ansi escape sequences at the end" ) { + std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me\033[38;2;98;174;239m"; + AnsiSkippingString str(text); + auto it = str.begin(); + CHECK(*it == 'a'); + ++it; + CHECK(*it == 'b'); + ++it; + CHECK(*it == 'c'); + ++it; + CHECK(*it == 'd'); + ++it; + CHECK(*it == 'e'); + ++it; + CHECK(it == str.end()); + --it; + CHECK(*it == 'e'); + --it; + CHECK(*it == 'd'); + --it; + CHECK(*it == 'c'); + --it; + CHECK(*it == 'b'); + --it; + CHECK(*it == 'a'); + CHECK(it == str.begin()); + } + + SECTION( "skips consecutive escapes" ) { + std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me"; + AnsiSkippingString str(text); + auto it = str.begin(); + CHECK(*it == 'a'); + ++it; + CHECK(*it == 'b'); + ++it; + CHECK(*it == 'c'); + ++it; + CHECK(*it == 'd'); + ++it; + CHECK(*it == 'e'); + ++it; + CHECK(it == str.end()); + --it; + CHECK(*it == 'e'); + --it; + CHECK(*it == 'd'); + --it; + CHECK(*it == 'c'); + --it; + CHECK(*it == 'b'); + --it; + CHECK(*it == 'a'); + CHECK(it == str.begin()); + } + + SECTION( "handles incomplete ansi sequences" ) { + std::string text = "a\033[b\033[30c\033[30;d\033[30;2e"; + AnsiSkippingString str(text); + CHECK(std::string(str.begin(), str.end()) == text); + } +} + +TEST_CASE( "TextFlow::AnsiSkippingString computes the size properly", + "[TextFlow][ansiskippingstring][approvals]" ) { + std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me"; + AnsiSkippingString str(text); + CHECK(str.size() == 5); +} + +TEST_CASE( "TextFlow::AnsiSkippingString substrings properly", + "[TextFlow][ansiskippingstring][approvals]" ) { + SECTION("basic test") { + std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me"; + AnsiSkippingString str(text); + auto a = str.begin(); + auto b = str.begin(); + ++b; + ++b; + CHECK(str.substring(a, b) == "a\033[38;2;98;174;239mb\033[38m"); + ++a; + ++b; + CHECK(str.substring(a, b) == "b\033[38mc\033[0m"); + CHECK(str.substring(a, str.end()) == "b\033[38mc\033[0md\033[me"); + CHECK(str.substring(str.begin(), str.end()) == text); + } + SECTION("escapes at the start") { + std::string text = "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38mc\033[0md\033[me"; + AnsiSkippingString str(text); + auto a = str.begin(); + auto b = str.begin(); + ++b; + ++b; + CHECK(str.substring(a, b) == "\033[38;2;98;174;239m\033[38;2;98;174;239ma\033[38;2;98;174;239mb\033[38m\033[38m\033[38m"); + ++a; + ++b; + CHECK(str.substring(a, b) == "b\033[38m\033[38m\033[38mc\033[0m"); + CHECK(str.substring(a, str.end()) == "b\033[38m\033[38m\033[38mc\033[0md\033[me"); + CHECK(str.substring(str.begin(), str.end()) == text); + } + SECTION("escapes at the end") { + std::string text = "a\033[38;2;98;174;239mb\033[38mc\033[0md\033[me\033[38m"; + AnsiSkippingString str(text); + auto a = str.begin(); + auto b = str.begin(); + ++b; + ++b; + CHECK(str.substring(a, b) == "a\033[38;2;98;174;239mb\033[38m"); + ++a; + ++b; + CHECK(str.substring(a, b) == "b\033[38mc\033[0m"); + CHECK(str.substring(a, str.end()) == "b\033[38mc\033[0md\033[me\033[38m"); + CHECK(str.substring(str.begin(), str.end()) == text); + } +} + +TEST_CASE( "TextFlow::Column skips ansi escape sequences", + "[TextFlow][column][approvals]" ) { + std::string text = "\033[38;2;98;174;239m\033[38;2;198;120;221mThe quick brown \033[38;2;198;120;221mfox jumped over the lazy dog\033[0m"; + Column col(text); + + SECTION( "width=20" ) { + col.width( 20 ); + REQUIRE( as_written( col ) == "\033[38;2;98;174;239m\033[38;2;198;120;221mThe quick brown \033[38;2;198;120;221mfox\n" + "jumped over the lazy\n" + "dog\033[0m" ); + } + + SECTION( "width=80" ) { + col.width( 80 ); + REQUIRE( as_written( col ) == text ); + } +} |