1package theme
2
3import (
4 "bytes"
5 "reflect"
6 "strconv"
7 "testing"
8
9 renderer "github.com/bnema/vev-vt"
10)
11
12type paletteEvent struct {
13 slot int
14 rgb renderer.RGB
15}
16
17func TestScannerExtractsPaletteResponses(t *testing.T) {
18 tests := []struct {
19 name string
20 chunks []string
21 wantPalette []paletteEvent
22 wantColors []int
23 wantOut string
24 }{
25 {
26 name: "BEL hash and ST rgb",
27 chunks: []string{"a\x1b]4;0;#112233\x07b\x1b]4;15;rgb:4455/6677/8899\x1b\\c"},
28 wantPalette: []paletteEvent{{slot: 0, rgb: renderer.RGB{R: 0x11, G: 0x22, B: 0x33}}, {slot: 15, rgb: renderer.RGB{R: 0x44, G: 0x66, B: 0x88}}},
29 wantOut: "abc",
30 },
31 {
32 name: "split prefix body and terminator",
33 chunks: []string{"a\x1b]4;", "1;rgb:12", "34/abcd/ffff\x1b", "\\b"},
34 wantPalette: []paletteEvent{{slot: 1, rgb: renderer.RGB{R: 0x12, G: 0xab, B: 0xff}}},
35 wantOut: "ab",
36 },
37 {
38 name: "interleaved defaults and multiple replies",
39 chunks: []string{"a\x1b]10;#010203\x07\x1b]4;2;#102030\x07\x1b]11;#040506\x07\x1b]4;2;rgb:aa/bb/cc\x07z"},
40 wantPalette: []paletteEvent{{slot: 2, rgb: renderer.RGB{R: 0x10, G: 0x20, B: 0x30}}, {slot: 2, rgb: renderer.RGB{R: 0xaa, G: 0xbb, B: 0xcc}}},
41 wantColors: []int{10, 11},
42 wantOut: "az",
43 },
44 }
45
46 for _, tt := range tests {
47 t.Run(tt.name, func(t *testing.T) {
48 var s Scanner
49 var palette []paletteEvent
50 var colors []int
51 var out bytes.Buffer
52 for _, chunk := range tt.chunks {
53 s.Scan([]byte(chunk), func(kind int, rgb renderer.RGB) {
54 colors = append(colors, kind)
55 }, func(slot int, rgb renderer.RGB) {
56 palette = append(palette, paletteEvent{slot: slot, rgb: rgb})
57 }, func(bool) {}, func(b []byte) {
58 out.Write(b)
59 })
60 }
61 if !reflect.DeepEqual(palette, tt.wantPalette) {
62 t.Fatalf("palette=%+v want %+v", palette, tt.wantPalette)
63 }
64 if !reflect.DeepEqual(colors, tt.wantColors) {
65 t.Fatalf("colors=%v want %v", colors, tt.wantColors)
66 }
67 if out.String() != tt.wantOut {
68 t.Fatalf("passthrough=%q want %q", out.String(), tt.wantOut)
69 }
70 })
71 }
72}
73
74func TestScannerReportsEveryValidPaletteSlot(t *testing.T) {
75 var data bytes.Buffer
76 for slot := 0; slot < 16; slot++ {
77 data.WriteString("\x1b]4;")
78 data.WriteString(strconv.Itoa(slot))
79 data.WriteString(";#010203\x07")
80 }
81
82 var s Scanner
83 var slots []int
84 var out bytes.Buffer
85 s.Scan(data.Bytes(), func(int, renderer.RGB) {}, func(slot int, rgb renderer.RGB) {
86 if rgb != (renderer.RGB{R: 1, G: 2, B: 3}) {
87 t.Fatalf("slot %d rgb=%+v", slot, rgb)
88 }
89 slots = append(slots, slot)
90 }, func(bool) {}, func(b []byte) {
91 out.Write(b)
92 })
93
94 if want := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; !reflect.DeepEqual(slots, want) {
95 t.Fatalf("slots=%v want %v", slots, want)
96 }
97 if out.Len() != 0 {
98 t.Fatalf("passthrough=%q want empty", out.String())
99 }
100}
101
102func TestScannerForwardsMalformedPaletteResponses(t *testing.T) {
103 const malformed = "\x1b]4;;#010203\x07" +
104 "\x1b]4;-1;#010203\x07" +
105 "\x1b]4;cat;#010203\x1b\\" +
106 "\x1b]4;16;#010203\x07" +
107 "\x1b]4;999999999999999999;#010203\x07" +
108 "\x1b]4;3;not-a-color\x07"
109
110 var s Scanner
111 var out bytes.Buffer
112 paletteCalls := 0
113 s.Scan([]byte("before"+malformed+"after"), func(int, renderer.RGB) {}, func(int, renderer.RGB) {
114 paletteCalls++
115 }, func(bool) {}, func(b []byte) {
116 out.Write(b)
117 })
118
119 if paletteCalls != 0 {
120 t.Fatalf("palette callbacks=%d want 0", paletteCalls)
121 }
122 if got, want := out.String(), "before"+malformed+"after"; got != want {
123 t.Fatalf("passthrough=%q want %q", got, want)
124 }
125}
126
127func TestScannerFlushesTruncatedPaletteResponseAtPendingLimit(t *testing.T) {
128 var s Scanner
129 var out bytes.Buffer
130 partial := append([]byte("\x1b]4;15;rgb:"), bytes.Repeat([]byte("a"), maxPending)...)
131 s.Scan(partial, func(int, renderer.RGB) {}, func(int, renderer.RGB) {}, func(bool) {}, func(b []byte) {
132 out.Write(b)
133 })
134 s.Scan([]byte("z"), func(int, renderer.RGB) {}, func(int, renderer.RGB) {}, func(bool) {}, func(b []byte) {
135 out.Write(b)
136 })
137
138 if got, want := out.String(), string(partial)+"z"; got != want {
139 t.Fatalf("passthrough=%q want %q", got, want)
140 }
141}