Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
diff options
context:
space:
mode:
authorJeremy Rifkin2024-05-04 16:43:52 -0500
committerGitHub2024-05-04 23:43:52 +0200
commit8ce2426e53b2596a9823b4a314f72f1c3b30be82 (patch)
treee8262448f9810687436a3e4cb39372691e7baa31 /tests/SelfTest/IntrospectiveTests/TextFlow.tests.cpp
parentfa5a53df17303339eff8fda5cae0b601af207085 (diff)
downloadcatch3-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.cpp200
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 );
+ }
+}