1package mouse
2
3import (
4 "bytes"
5 "reflect"
6 "testing"
7)
8
9type scanToken struct {
10 kind string
11 data []byte
12 event Event
13}
14
15func collectScan(s *Scanner, chunks ...[]byte) []scanToken {
16 var tokens []scanToken
17 for _, chunk := range chunks {
18 s.Scan(chunk, func(ev Event) {
19 tokens = append(tokens, scanToken{kind: "mouse", event: ev})
20 }, func(b []byte) {
21 tokens = append(tokens, scanToken{kind: "bytes", data: append([]byte(nil), b...)})
22 })
23 }
24 return tokens
25}
26
27func TestScanDecodesWheelClickAndDrag(t *testing.T) {
28 tests := []struct {
29 name string
30 in []byte
31 want Event
32 }{
33 {
34 name: "wheel up",
35 in: []byte("\x1b[<64;10;20M"),
36 want: Event{Type: Press, Button: WheelUp, Col: 9, Row: 19, Raw: []byte("\x1b[<64;10;20M")},
37 },
38 {
39 name: "wheel down",
40 in: []byte("\x1b[<65;3;4M"),
41 want: Event{Type: Press, Button: WheelDown, Col: 2, Row: 3, Raw: []byte("\x1b[<65;3;4M")},
42 },
43 {
44 name: "left press",
45 in: []byte("\x1b[<0;1;2M"),
46 want: Event{Type: Press, Button: Left, Col: 0, Row: 1, Raw: []byte("\x1b[<0;1;2M")},
47 },
48 {
49 name: "left release",
50 in: []byte("\x1b[<0;1;2m"),
51 want: Event{Type: Release, Button: Left, Col: 0, Row: 1, Raw: []byte("\x1b[<0;1;2m")},
52 },
53 {
54 name: "right motion",
55 in: []byte("\x1b[<34;7;8M"),
56 want: Event{Type: Motion, Button: Right, Col: 6, Row: 7, Raw: []byte("\x1b[<34;7;8M")},
57 },
58 }
59
60 for _, tt := range tests {
61 t.Run(tt.name, func(t *testing.T) {
62 tokens := collectScan(&Scanner{}, tt.in)
63 if len(tokens) != 1 || tokens[0].kind != "mouse" {
64 t.Fatalf("tokens = %#v, want one mouse token", tokens)
65 }
66 if !reflect.DeepEqual(tokens[0].event, tt.want) {
67 t.Fatalf("event = %#v, want %#v", tokens[0].event, tt.want)
68 }
69 })
70 }
71}
72
73func TestScanCoalescedEvents(t *testing.T) {
74 tokens := collectScan(&Scanner{}, []byte("\x1b[<0;1;1M\x1b[<65;2;3M"))
75 if len(tokens) != 2 || tokens[0].kind != "mouse" || tokens[1].kind != "mouse" {
76 t.Fatalf("tokens = %#v, want two mouse tokens", tokens)
77 }
78 if tokens[0].event.Button != Left || tokens[1].event.Button != WheelDown {
79 t.Fatalf("buttons = %v, %v; want Left, WheelDown", tokens[0].event.Button, tokens[1].event.Button)
80 }
81}
82
83func TestScanBuffersSplitReportAfterIntroducer(t *testing.T) {
84 var s Scanner
85 first := collectScan(&s, []byte("\x1b[<64;"))
86 if len(first) != 0 {
87 t.Fatalf("first scan tokens = %#v, want none while pending", first)
88 }
89 second := collectScan(&s, []byte("5;6M"))
90 if len(second) != 1 || second[0].kind != "mouse" {
91 t.Fatalf("second scan tokens = %#v, want one mouse token", second)
92 }
93 want := Event{Type: Press, Button: WheelUp, Col: 4, Row: 5, Raw: []byte("\x1b[<64;5;6M")}
94 if !reflect.DeepEqual(second[0].event, want) {
95 t.Fatalf("event = %#v, want %#v", second[0].event, want)
96 }
97}
98
99func TestScanPreservesInterleavedOrdering(t *testing.T) {
100 tokens := collectScan(&Scanner{}, []byte("a\x1b[<0;1;1Mb\x1b[<65;2;2Mc"))
101 want := []scanToken{
102 {kind: "bytes", data: []byte("a")},
103 {kind: "mouse", event: Event{Type: Press, Button: Left, Col: 0, Row: 0, Raw: []byte("\x1b[<0;1;1M")}},
104 {kind: "bytes", data: []byte("b")},
105 {kind: "mouse", event: Event{Type: Press, Button: WheelDown, Col: 1, Row: 1, Raw: []byte("\x1b[<65;2;2M")}},
106 {kind: "bytes", data: []byte("c")},
107 }
108 if !reflect.DeepEqual(tokens, want) {
109 t.Fatalf("tokens = %#v, want %#v", tokens, want)
110 }
111}
112
113func TestScanMalformedBailsToBytes(t *testing.T) {
114 tests := [][]byte{
115 []byte("\x1b[<x"),
116 []byte("\x1b[<0;;1M"),
117 []byte("\x1b[<0;0;1M"),
118 []byte("\x1b[<0;1M"),
119 }
120 for _, in := range tests {
121 t.Run(string(bytes.ReplaceAll(in, []byte("\x1b"), []byte("ESC"))), func(t *testing.T) {
122 tokens := collectScan(&Scanner{}, in)
123 if len(tokens) != 1 || tokens[0].kind != "bytes" || !bytes.Equal(tokens[0].data, in) {
124 t.Fatalf("tokens = %#v, want one byte token %q", tokens, in)
125 }
126 })
127 }
128}
129
130func TestScanPassesThroughBareESCAndESCBracket(t *testing.T) {
131 tests := [][]byte{
132 []byte("\x1b"),
133 []byte("\x1b["),
134 []byte("x\x1b"),
135 []byte("x\x1b["),
136 }
137 for _, in := range tests {
138 t.Run(string(bytes.ReplaceAll(in, []byte("\x1b"), []byte("ESC"))), func(t *testing.T) {
139 tokens := collectScan(&Scanner{}, in)
140 if len(tokens) != 1 || tokens[0].kind != "bytes" || !bytes.Equal(tokens[0].data, in) {
141 t.Fatalf("tokens = %#v, want passthrough %q", tokens, in)
142 }
143 })
144 }
145}