| 1 | package cli |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | |
| 7 | "github.com/charmbracelet/colorprofile" |
| 8 | ) |
| 9 | |
| 10 | func testSweepRows(width int) (before, after []string) { |
| 11 | // mix wide runes, pre-styled text and plain text |
| 12 | for range 4 { |
| 13 | before = append(before, strings.Repeat("宽", width/2), strings.Repeat("a", width), |
| 14 | themeFg(activeCLITheme.warn, strings.Repeat("s", width))) |
| 15 | after = append(after, strings.Repeat("窄", width/2), strings.Repeat("b", width), |
| 16 | themeFg(activeCLITheme.info, strings.Repeat("t", width))) |
| 17 | } |
| 18 | return before, after |
| 19 | } |
| 20 | |
| 21 | func TestThemeSweepHoldsExactRowWidth(t *testing.T) { |
| 22 | defer restoreThemeForTest(activeColorProfile, activeCLITheme) |
| 23 | configureCLIThemeWithStyle("dark", "graphite") |
| 24 | |
| 25 | for _, profile := range []colorprofile.Profile{colorprofile.ANSI256, colorprofile.TrueColor} { |
| 26 | activeColorProfile = profile |
| 27 | const width = 40 |
| 28 | before, after := testSweepRows(width) |
| 29 | s := &themeSweep{before: before, after: after, step: 3, width: width} |
| 30 | for s.col = 0; s.col <= width; s.col++ { |
| 31 | for i, row := range strings.Split(s.render(), "\n") { |
| 32 | if got := visibleWidth(row); got != width { |
| 33 | t.Fatalf("%v col=%d row=%d width=%d, want %d: %q", profile, s.col, i, got, width, row) |
| 34 | } |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | func TestThemeSweepAdvanceTerminates(t *testing.T) { |
| 41 | s := &themeSweep{before: []string{"a"}, after: []string{"b"}, step: 3, width: 80} |
| 42 | steps := 0 |
| 43 | for s.advance() { |
| 44 | steps++ |
| 45 | if steps > themeSweepFrames*4 { |
| 46 | t.Fatal("sweep never reached the right edge") |
| 47 | } |
| 48 | } |
| 49 | if s.col < s.width { |
| 50 | t.Fatalf("sweep stopped at col %d, want >= %d", s.col, s.width) |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | func TestThemeSweepSkippedWhenTerminalCannotCarryIt(t *testing.T) { |
| 55 | defer restoreThemeForTest(activeColorProfile, activeCLITheme) |
| 56 | configureCLIThemeWithStyle("dark", "graphite") |
| 57 | dark := activeCLITheme |
| 58 | light := resolveCLIThemeWithStyle("light", "sandstone") |
| 59 | |
| 60 | for _, tt := range []struct { |
| 61 | name string |
| 62 | profile colorprofile.Profile |
| 63 | width int |
| 64 | state tuiState |
| 65 | from cliPalette |
| 66 | to cliPalette |
| 67 | }{ |
| 68 | {name: "no colour", profile: colorprofile.NoTTY, width: 80, from: dark, to: light}, |
| 69 | {name: "too narrow", profile: colorprofile.ANSI256, width: 8, from: dark, to: light}, |
| 70 | {name: "turn running", profile: colorprofile.ANSI256, width: 80, state: tuiRunning, from: dark, to: light}, |
| 71 | {name: "same theme", profile: colorprofile.ANSI256, width: 80, from: dark, to: dark}, |
| 72 | } { |
| 73 | t.Run(tt.name, func(t *testing.T) { |
| 74 | activeColorProfile = tt.profile |
| 75 | m := newTestChatTUI() |
| 76 | m.width = tt.width |
| 77 | m.state = tt.state |
| 78 | if cmd := m.startThemeSweep(tt.from, tt.to); cmd != nil { |
| 79 | t.Fatal("sweep should be skipped, switch must stay instant") |
| 80 | } |
| 81 | if m.themeSweep != nil { |
| 82 | t.Fatal("skipped sweep must not freeze the frame") |
| 83 | } |
| 84 | }) |
| 85 | } |
| 86 | } |
| 87 |