@@ -381,6 +381,81 @@ TEST_CASE( "TextFlow::AnsiSkippingString substrings properly",
381381 }
382382}
383383
384+ TEST_CASE ( " TextFlow::AnsiSkippingString counts UTF-8 codepoints" ,
385+ " [TextFlow][ansiskippingstring][approvals]" ) {
386+ SECTION ( " 2-byte codepoints" ) {
387+ AnsiSkippingString str ( " \xC3\xA4\xC3\xB6\xC3\xBC " ); // äöü
388+ CHECK ( str.size () == 3 );
389+ }
390+ SECTION ( " 3-byte codepoints" ) {
391+ AnsiSkippingString str ( " \xE4\xB8\xAD\xE6\x96\x87 " ); // 中文
392+ CHECK ( str.size () == 2 );
393+ }
394+ SECTION ( " 4-byte codepoints" ) {
395+ // U+1F600 U+1F60E
396+ AnsiSkippingString str ( " \xF0\x9F\x98\x80\xF0\x9F\x98\x8E " );
397+ CHECK ( str.size () == 2 );
398+ }
399+ SECTION ( " mixed ASCII and UTF-8" ) {
400+ AnsiSkippingString str ( " a\xC3\xA4 " " b" ); // aäb
401+ CHECK ( str.size () == 3 );
402+ }
403+ SECTION ( " UTF-8 with ANSI escapes" ) {
404+ AnsiSkippingString str ( " \033 [31m\xC3\xA4\xC3\xB6\xC3\xBC\033 [0m" );
405+ CHECK ( str.size () == 3 );
406+ }
407+ }
408+
409+ TEST_CASE ( " TextFlow::AnsiSkippingString iterates UTF-8 codepoints" ,
410+ " [TextFlow][ansiskippingstring][approvals]" ) {
411+ // "aäb" = 'a' (0x61), 'ä' (0xC3 0xA4), 'b' (0x62)
412+ std::string text = " a\xC3\xA4 " " b" ;
413+ AnsiSkippingString str ( text );
414+
415+ SECTION ( " forward iteration has correct count" ) {
416+ int count = 0 ;
417+ for ( auto it = str.begin (); it != str.end (); ++it ) {
418+ ++count;
419+ }
420+ CHECK ( count == 3 );
421+ }
422+ SECTION ( " backward iteration has correct count" ) {
423+ auto it = str.end ();
424+ int count = 0 ;
425+ while ( it != str.begin () ) {
426+ --it;
427+ ++count;
428+ }
429+ CHECK ( count == 3 );
430+ }
431+ SECTION ( " substring preserves full UTF-8 bytes" ) {
432+ auto a = str.begin ();
433+ auto b = str.begin ();
434+ ++b; // past 'a'
435+ ++b; // past 'ä'
436+ CHECK ( str.substring ( a, b ) == " a\xC3\xA4 " );
437+ }
438+ }
439+
440+ TEST_CASE ( " TextFlow::Column wraps UTF-8 text correctly" ,
441+ " [TextFlow][column][approvals]" ) {
442+ // "äöü äöü äöü" = 11 codepoints, 17 bytes
443+ Column col ( " \xC3\xA4\xC3\xB6\xC3\xBC \xC3\xA4\xC3\xB6\xC3\xBC \xC3\xA4\xC3\xB6\xC3\xBC " );
444+
445+ SECTION ( " width=8" ) {
446+ col.width ( 8 );
447+ // 7 visible codepoints "äöü äöü" fit, then wrap
448+ REQUIRE ( as_written ( col ) ==
449+ " \xC3\xA4\xC3\xB6\xC3\xBC \xC3\xA4\xC3\xB6\xC3\xBC\n "
450+ " \xC3\xA4\xC3\xB6\xC3\xBC " );
451+ }
452+ SECTION ( " width=80" ) {
453+ col.width ( 80 );
454+ REQUIRE ( as_written ( col ) ==
455+ " \xC3\xA4\xC3\xB6\xC3\xBC \xC3\xA4\xC3\xB6\xC3\xBC \xC3\xA4\xC3\xB6\xC3\xBC " );
456+ }
457+ }
458+
384459TEST_CASE ( " TextFlow::Column skips ansi escape sequences" ,
385460 " [TextFlow][column][approvals]" ) {
386461 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" ;
0 commit comments