@@ -383,6 +383,182 @@ func TestColoredString(t *testing.T) {
383383 assert .Equal (t , expected , table .String ())
384384}
385385
386+ // TestRuneLenWithFatihColorFormats tests that runeLen correctly handles
387+ // all ANSI escape sequences produced by github.com/fatih/color library.
388+ // This prevents regression when handling colored output.
389+ func TestRuneLenWithFatihColorFormats (t * testing.T ) {
390+ tests := []struct {
391+ name string
392+ input string
393+ expected int
394+ }{
395+ // Basic colors (single number): \x1b[31m (red), \x1b[32m (green), etc.
396+ {
397+ name : "fatih/color basic red" ,
398+ input : "\x1b [31mhello\x1b [0m" ,
399+ expected : 5 ,
400+ },
401+ {
402+ name : "fatih/color basic green" ,
403+ input : "\x1b [32mworld\x1b [0m" ,
404+ expected : 5 ,
405+ },
406+ // Bold attribute (single number): \x1b[1m
407+ {
408+ name : "fatih/color bold" ,
409+ input : "\x1b [1mbold text\x1b [0m" ,
410+ expected : 9 ,
411+ },
412+ // Hi-intensity colors: \x1b[90m to \x1b[97m
413+ {
414+ name : "fatih/color hi-intensity" ,
415+ input : "\x1b [91mhi-red\x1b [0m" ,
416+ expected : 6 ,
417+ },
418+ // Combined attributes (two numbers): \x1b[1;31m (bold red)
419+ {
420+ name : "fatih/color bold+red" ,
421+ input : "\x1b [1;31mbold red\x1b [0m" ,
422+ expected : 8 ,
423+ },
424+ // Background colors: \x1b[41m (red bg)
425+ {
426+ name : "fatih/color background" ,
427+ input : "\x1b [41mred bg\x1b [0m" ,
428+ expected : 6 ,
429+ },
430+ // Foreground + Background: \x1b[31;47m
431+ {
432+ name : "fatih/color fg+bg" ,
433+ input : "\x1b [31;47mred on white\x1b [0m" ,
434+ expected : 12 ,
435+ },
436+ // 256-color mode: \x1b[38;5;196m (foreground) or \x1b[48;5;196m (background)
437+ {
438+ name : "fatih/color 256-color foreground" ,
439+ input : "\x1b [38;5;196m256 color\x1b [0m" ,
440+ expected : 9 ,
441+ },
442+ {
443+ name : "fatih/color 256-color background" ,
444+ input : "\x1b [48;5;21mblue bg\x1b [0m" ,
445+ expected : 7 ,
446+ },
447+ // 24-bit RGB: \x1b[38;2;255;128;0m (foreground orange)
448+ {
449+ name : "fatih/color RGB foreground" ,
450+ input : "\x1b [38;2;255;128;0morange\x1b [0m" ,
451+ expected : 6 ,
452+ },
453+ {
454+ name : "fatih/color RGB background" ,
455+ input : "\x1b [48;2;0;0;255mblue bg\x1b [0m" ,
456+ expected : 7 ,
457+ },
458+ // Mixed RGB foreground + background
459+ {
460+ name : "fatih/color RGB fg+bg" ,
461+ input : "\x1b [38;2;255;255;255m\x1b [48;2;0;0;0mwhite on black\x1b [0m" ,
462+ expected : 14 ,
463+ },
464+ // Underline and other attributes: \x1b[4m
465+ {
466+ name : "fatih/color underline" ,
467+ input : "\x1b [4munderlined\x1b [0m" ,
468+ expected : 10 ,
469+ },
470+ // Nested/chained attributes
471+ {
472+ name : "fatih/color nested styles" ,
473+ input : "\x1b [1m\x1b [4m\x1b [31mbold underline red\x1b [0m" ,
474+ expected : 18 ,
475+ },
476+ // Specific reset codes: \x1b[22m (reset bold), \x1b[24m (reset underline)
477+ {
478+ name : "fatih/color specific resets" ,
479+ input : "\x1b [1mbold\x1b [22m normal\x1b [0m" ,
480+ expected : 11 ,
481+ },
482+ // Multiple resets in sequence
483+ {
484+ name : "fatih/color multiple resets" ,
485+ input : "\x1b [1m\x1b [4mtext\x1b [22m\x1b [24m\x1b [0m" ,
486+ expected : 4 ,
487+ },
488+ // Empty string with just escape codes
489+ {
490+ name : "fatih/color only escapes" ,
491+ input : "\x1b [31m\x1b [0m" ,
492+ expected : 0 ,
493+ },
494+ // Real-world example: colored status output
495+ {
496+ name : "fatih/color real status" ,
497+ input : "\x1b [32m✓\x1b [0m \x1b [1mSuccess\x1b [0m" ,
498+ expected : 9 , // "✓ Success" = 1 + 1 + 7
499+ },
500+ }
501+
502+ for _ , tt := range tests {
503+ t .Run (tt .name , func (t * testing.T ) {
504+ got := runeLen (tt .input )
505+ assert .Equal (t , tt .expected , got , "runeLen(%q) = %d, want %d" , tt .input , got , tt .expected )
506+ })
507+ }
508+ }
509+
510+ // TestTableColumnWidthWithFatihColor ensures table column width calculation
511+ // works correctly with various fatih/color escape sequences.
512+ func TestTableColumnWidthWithFatihColor (t * testing.T ) {
513+ tests := []struct {
514+ name string
515+ rows []Row
516+ expectedSizes []int
517+ }{
518+ {
519+ name : "basic colors" ,
520+ rows : []Row {
521+ {"\x1b [31mred\x1b [0m" , "normal" },
522+ {"plain" , "\x1b [32mgreen\x1b [0m" },
523+ },
524+ expectedSizes : []int {5 , 6 },
525+ },
526+ {
527+ name : "256 colors" ,
528+ rows : []Row {
529+ {"\x1b [38;5;196mcolor256\x1b [0m" , "test" },
530+ },
531+ expectedSizes : []int {8 , 4 },
532+ },
533+ {
534+ name : "RGB colors" ,
535+ rows : []Row {
536+ {"\x1b [38;2;255;128;0mRGB orange\x1b [0m" , "data" },
537+ },
538+ expectedSizes : []int {10 , 4 },
539+ },
540+ {
541+ name : "mixed styles" ,
542+ rows : []Row {
543+ {"\x1b [1;31mbold red\x1b [0m" , "\x1b [4munderline\x1b [0m" },
544+ {"\x1b [38;2;0;255;0mRGB green\x1b [0m" , "plain" },
545+ },
546+ expectedSizes : []int {9 , 9 },
547+ },
548+ }
549+
550+ for _ , tt := range tests {
551+ t .Run (tt .name , func (t * testing.T ) {
552+ table := NewTable ()
553+ for _ , row := range tt .rows {
554+ table .AddRow (row )
555+ }
556+ sizes := table .columnsSize ()
557+ assert .Equal (t , tt .expectedSizes , sizes )
558+ })
559+ }
560+ }
561+
386562func TestResizeLargestColumnOnWhitespace (t * testing.T ) {
387563 tb := NewTable ()
388564 tb .AddRow (Row {"1" , "abc def ghi jk" })
0 commit comments