1package terminalcap
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/require"
7)
8
9func TestColorCapabilities(t *testing.T) {
10 tests := []struct {
11 name string
12 in ColorCapabilities
13 wantRGB bool
14 }{
15 {name: "zero value is truecolor", in: ColorCapabilities{}, wantRGB: true},
16 {name: "truecolor", in: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, wantRGB: true},
17 {name: "ansi256", in: ColorCapabilities{Mode: ANSI256}, wantRGB: false},
18 {name: "ansi16", in: ColorCapabilities{Mode: ANSI16, Source: SourceForced}, wantRGB: false},
19 {name: "monochrome", in: ColorCapabilities{Mode: Monochrome, Source: SourceForced}, wantRGB: false},
20 }
21 for _, tt := range tests {
22 t.Run(tt.name, func(t *testing.T) {
23 require.Equal(t, tt.wantRGB, tt.in.RGB())
24 })
25 }
26}
27
28func TestColorCapabilitiesValid(t *testing.T) {
29 tests := []struct {
30 name string
31 in ColorCapabilities
32 want bool
33 }{
34 {name: "zero value", in: ColorCapabilities{}, want: true},
35 {name: "every mode and source", in: ColorCapabilities{Mode: Monochrome, Source: SourceForced}, want: true},
36 {name: "unknown mode", in: ColorCapabilities{Mode: Monochrome + 1}},
37 {name: "unknown source", in: ColorCapabilities{Source: SourceForced + 1}},
38 }
39 for _, tt := range tests {
40 t.Run(tt.name, func(t *testing.T) {
41 require.Equal(t, tt.want, tt.in.Valid())
42 })
43 }
44}
45
46func TestResolve(t *testing.T) {
47 tests := []struct {
48 name string
49 env []string
50 declared ColorCapabilities
51 want ColorCapabilities
52 wantApp Application
53 }{
54 {name: "unknown claim falls back to detection", env: []string{"TERM=xterm-256color"}, want: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}},
55 {name: "unknown truecolor claim falls back to detection", env: []string{"TERM=xterm-256color"}, declared: ColorCapabilities{Mode: TrueColor}, want: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}},
56 {name: "unknown claim with detected truecolor", env: []string{"TERM=xterm-256color", "COLORTERM=truecolor"}, want: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}},
57 {name: "declared truecolor overrides indexed detection", env: []string{"TERM=xterm-256color"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, want: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}},
58 {name: "declared truecolor on unknown terminal", env: []string{"TERM=unknown"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, want: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}},
59 {name: "declared 256 overrides detected truecolor verbatim", env: []string{"TERM=xterm-256color", "COLORTERM=truecolor"}, declared: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}, want: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}},
60 {name: "forced 16 colors", env: []string{"TERM=xterm-256color", "COLORTERM=truecolor"}, declared: ColorCapabilities{Mode: ANSI16, Source: SourceForced}, want: ColorCapabilities{Mode: ANSI16, Source: SourceForced}},
61 {name: "forced monochrome", env: nil, declared: ColorCapabilities{Mode: Monochrome, Source: SourceForced}, want: ColorCapabilities{Mode: Monochrome, Source: SourceForced}},
62 {name: "heuristic truecolor upgrades weaker detection", env: []string{"TERM=xterm-256color"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceHeuristic}, want: ColorCapabilities{Mode: TrueColor, Source: SourceHeuristic}},
63 {name: "heuristic truecolor keeps stronger detection", env: []string{"TERM=xterm-kitty", "KITTY_WINDOW_ID=1"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceHeuristic}, want: ColorCapabilities{Mode: TrueColor, Source: SourceHeuristic}, wantApp: ApplicationKitty},
64 {name: "heuristic 16 colors falls back to detection", env: []string{"TERM=xterm-256color"}, declared: ColorCapabilities{Mode: ANSI16, Source: SourceHeuristic}, want: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}},
65 {name: "declared claim keeps detected application", env: []string{"TERM=tmux-256color", "KITTY_WINDOW_ID=1"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, want: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, wantApp: ApplicationKitty},
66 }
67 for _, tt := range tests {
68 t.Run(tt.name, func(t *testing.T) {
69 got := Resolve(tt.env, tt.declared)
70 require.Equal(t, tt.want, got.Color)
71 require.Equal(t, tt.wantApp, got.Application)
72 require.False(t, got.KittyGraphics)
73 })
74 }
75}
76
77func TestDetect(t *testing.T) {
78 tests := []struct {
79 name string
80 env []string
81 wantMode ColorMode
82 wantSource Source
83 wantApp Application
84 wantGraphics bool
85 }{
86 {name: "declared truecolor", env: []string{"TERM=xterm-256color", "COLORTERM=truecolor"}, wantMode: TrueColor, wantSource: SourceDeclared},
87 {name: "declared 24bit", env: []string{"TERM=xterm-256color", "COLORTERM=24bit"}, wantMode: TrueColor, wantSource: SourceDeclared},
88 {name: "direct terminfo entry", env: []string{"TERM=foot-direct"}, wantMode: TrueColor, wantSource: SourceDeclared},
89 {name: "kitty signals infer truecolor", env: []string{"TERM=xterm-kitty", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceHeuristic, wantApp: ApplicationKitty},
90 {name: "kitty environment with declared truecolor remains color-only", env: []string{"TERM=xterm-kitty", "COLORTERM=truecolor", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceDeclared, wantApp: ApplicationKitty},
91 {name: "256 color terminal remains a constrained attachment", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared},
92 {name: "unknown terminal is conservatively indexed", env: []string{"TERM=unknown"}, wantMode: ANSI256, wantSource: SourceUnknown},
93 {name: "empty TERM is unknown", env: nil, wantMode: ANSI256, wantSource: SourceUnknown},
94 {name: "TERM=screen is unknown", env: []string{"TERM=screen"}, wantMode: ANSI256, wantSource: SourceUnknown},
95 {name: "TERM=xterm is unknown", env: []string{"TERM=xterm"}, wantMode: ANSI256, wantSource: SourceUnknown},
96 {name: "screen-256color", env: []string{"TERM=screen-256color"}, wantMode: ANSI256, wantSource: SourceDeclared},
97 {name: "tmux-256color", env: []string{"TERM=tmux-256color"}, wantMode: ANSI256, wantSource: SourceDeclared},
98 {name: "TERM is case-insensitive and trimmed", env: []string{"TERM= LINUX "}, wantMode: ANSI16, wantSource: SourceDeclared},
99 {name: "dumb is monochrome", env: []string{"TERM=dumb"}, wantMode: Monochrome, wantSource: SourceDeclared},
100 {name: "vt52 is monochrome", env: []string{"TERM=vt52"}, wantMode: Monochrome, wantSource: SourceDeclared},
101 {name: "vt100 is monochrome", env: []string{"TERM=vt100"}, wantMode: Monochrome, wantSource: SourceDeclared},
102 {name: "vt102 is monochrome", env: []string{"TERM=vt102"}, wantMode: Monochrome, wantSource: SourceDeclared},
103 {name: "vt220 is monochrome", env: []string{"TERM=vt220"}, wantMode: Monochrome, wantSource: SourceDeclared},
104 {name: "-mono suffix is monochrome", env: []string{"TERM=xterm-mono"}, wantMode: Monochrome, wantSource: SourceDeclared},
105 {name: "-m suffix is monochrome", env: []string{"TERM=rxvt-m"}, wantMode: Monochrome, wantSource: SourceDeclared},
106 {name: "vt320 is not assumed monochrome", env: []string{"TERM=vt320"}, wantMode: ANSI256, wantSource: SourceUnknown},
107 {name: "linux console is 16 colors", env: []string{"TERM=linux"}, wantMode: ANSI16, wantSource: SourceDeclared},
108 {name: "ansi is 16 colors", env: []string{"TERM=ansi"}, wantMode: ANSI16, wantSource: SourceDeclared},
109 {name: "cons25 is 16 colors", env: []string{"TERM=cons25"}, wantMode: ANSI16, wantSource: SourceDeclared},
110 {name: "-16color suffix", env: []string{"TERM=xterm-16color"}, wantMode: ANSI16, wantSource: SourceDeclared},
111 {name: "-color suffix", env: []string{"TERM=xterm-color"}, wantMode: ANSI16, wantSource: SourceDeclared},
112 {name: "screen-color suffix", env: []string{"TERM=screen-color"}, wantMode: ANSI16, wantSource: SourceDeclared},
113 {name: "256color is not the -color suffix", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared},
114 {name: "COLORTERM truecolor beats linux", env: []string{"TERM=linux", "COLORTERM=truecolor"}, wantMode: TrueColor, wantSource: SourceDeclared},
115 {name: "COLORTERM 24bit beats dumb", env: []string{"TERM=dumb", "COLORTERM=24bit"}, wantMode: TrueColor, wantSource: SourceDeclared},
116 {name: "direct terminfo beats mono suffix", env: []string{"TERM=foot-direct"}, wantMode: TrueColor, wantSource: SourceDeclared},
117 {name: "unrelated COLORTERM does not change TERM rules", env: []string{"TERM=linux", "COLORTERM=yes"}, wantMode: ANSI16, wantSource: SourceDeclared},
118 {name: "kitty identity beats TERM rules", env: []string{"TERM=xterm-kitty", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceHeuristic, wantApp: ApplicationKitty},
119 {name: "kitty environment behind a multiplexer is not graphics evidence", env: []string{"TERM=tmux-256color", "KITTY_WINDOW_ID=1"}, wantMode: ANSI256, wantSource: SourceDeclared, wantApp: ApplicationKitty},
120 }
121
122 for _, tt := range tests {
123 t.Run(tt.name, func(t *testing.T) {
124 got := Detect(tt.env)
125 require.Equal(t, tt.wantMode, got.Color.Mode)
126 require.Equal(t, tt.wantSource, got.Color.Source)
127 require.Equal(t, tt.wantApp, got.Application)
128 require.Equal(t, tt.wantGraphics, got.SupportsKittyGraphics())
129 })
130 }
131}
132
133func TestParseColorMode(t *testing.T) {
134 tests := []struct {
135 name string
136 in string
137 wantMode ColorMode
138 wantAuto bool
139 wantErr bool
140 }{
141 {name: "empty is auto", in: "", wantAuto: true},
142 {name: "blank is auto", in: " \t", wantAuto: true},
143 {name: "auto", in: "auto", wantAuto: true},
144 {name: "auto is case-insensitive", in: " AUTO ", wantAuto: true},
145 {name: "truecolor", in: "truecolor", wantMode: TrueColor},
146 {name: "truecolor mixed case", in: "TrueColor", wantMode: TrueColor},
147 {name: "256", in: "256", wantMode: ANSI256},
148 {name: "16", in: " 16 ", wantMode: ANSI16},
149 {name: "mono", in: "mono", wantMode: Monochrome},
150 {name: "mono upper case", in: "MONO", wantMode: Monochrome},
151 {name: "24bit is not accepted", in: "24bit", wantErr: true},
152 {name: "256color is not accepted", in: "256color", wantErr: true},
153 {name: "number out of set", in: "8", wantErr: true},
154 {name: "monochrome spelled out is not accepted", in: "monochrome", wantErr: true},
155 {name: "garbage", in: "rainbow", wantErr: true},
156 }
157 for _, tt := range tests {
158 t.Run(tt.name, func(t *testing.T) {
159 mode, auto, err := ParseColorMode(tt.in)
160 if tt.wantErr {
161 require.Error(t, err)
162 require.False(t, auto)
163 return
164 }
165 require.NoError(t, err)
166 require.Equal(t, tt.wantAuto, auto)
167 if !auto {
168 require.Equal(t, tt.wantMode, mode)
169 }
170 })
171 }
172}