v0.7.1 ff50ee4

query_test.go RawHistoryFolder

1package terminalquery
2
3import (
4	"testing"
5
6	"github.com/stretchr/testify/require"
7)
8
9func TestProbeRequiresKittyAndDA1(t *testing.T) {
10	for _, tc := range []struct {
11		name      string
12		data      string
13		kitty     bool
14		da1       bool
15		unrelated string
16	}{
17		{name: "both and input", data: "x\x1b[?1;2c" + "\x1b_Gi=31;OK\x1b\\y", kitty: true, da1: true, unrelated: "xy"},
18		{name: "kitty DA1 with trailing separator", data: "\x1b_Gi=31;OK\x1b\\\x1b[?62;52;c", kitty: true, da1: true},
19		{name: "secondary DA is insufficient", data: "\x1b[>1;2c\x1b_Gi=31;OK\x1b\\", kitty: true, unrelated: "\x1b[>1;2c"},
20		{name: "ordinary input", data: "hello", unrelated: "hello"},
21	} {
22		t.Run(tc.name, func(t *testing.T) {
23			var p Probe
24			got := append(p.Feed([]byte(tc.data)), p.Finish()...)
25			require.Equal(t, tc.kitty, p.KittyGraphics())
26			require.Equal(t, tc.da1, p.DA1())
27			require.Equal(t, tc.unrelated, string(got))
28		})
29	}
30}
31
32func TestProbeRecognizesFragmentedResponsesAndReplaysInput(t *testing.T) {
33	var p Probe
34	var unrelated []byte
35	for _, fragment := range []string{"a\x1b", "[?1;2", "c", "b\x1b_Gi=31;", "OK\x1b", "\\z"} {
36		unrelated = append(unrelated, p.Feed([]byte(fragment))...)
37	}
38	unrelated = append(unrelated, p.Finish()...)
39	require.True(t, p.Ready())
40	require.Equal(t, "abz", string(unrelated))
41}
42
43func TestProbeRequiresExactCapabilityResponses(t *testing.T) {
44	for _, data := range []string{
45		"\x1b_Gi=31;OK,extra\x1b\\",
46		"\x1b_Gi=310;OK\x1b\\",
47		"\x1b_Gi=31;OKAY\x1b\\",
48		"\x1b[?1;;2c",
49		"\x1b[?x;2c",
50	} {
51		var p Probe
52		got := append(p.Feed([]byte(data)), p.Finish()...)
53		require.False(t, p.Ready(), "response %q was accepted", data)
54		require.Equal(t, data, string(got))
55	}
56}
57
58func FuzzProbeNeverPanics(f *testing.F) {
59	f.Add([]byte("text\x1b[?1;2c\x1b_Gi=31;OK\x1b\\"))
60	f.Add([]byte("\x1b_G"))
61	f.Fuzz(func(t *testing.T, data []byte) {
62		var p Probe
63		_ = p.Feed(data)
64		_ = p.Finish()
65	})
66}
67
68func TestProbeDoesNotRetainLargeInputFragments(t *testing.T) {
69	var p Probe
70	input := append(make([]byte, 1<<20), []byte("\x1b_G")...)
71	got := append(p.Feed(input), p.Finish()...)
72	require.Equal(t, input, got)
73	require.LessOrEqual(t, cap(p.pending), maxProbeResponseBytes+2)
74}
75
76func TestProbeBoundsUnterminatedResponse(t *testing.T) {
77	var p Probe
78	input := append([]byte("prefix"), []byte("\x1b_G")...)
79	input = append(input, make([]byte, maxProbeResponseBytes+1)...)
80	got := append(p.Feed(input), p.Finish()...)
81	require.False(t, p.Ready())
82	require.Equal(t, input, got)
83}
84
85func TestProbeRecognizesKittyKeyboardResponse(t *testing.T) {
86	for _, tc := range []struct {
87		name      string
88		fragments []string
89		keyboard  bool
90		da1       bool
91		unrelated string
92	}{
93		{name: "supported", fragments: []string{"\x1b[?0u\x1b[?62;c"}, keyboard: true, da1: true},
94		{name: "supported with flags", fragments: []string{"\x1b[?15u"}, keyboard: true},
95		{name: "unsupported", fragments: []string{"\x1b[?62;22c"}, da1: true},
96		{name: "fragmented", fragments: []string{"a\x1b", "[?", "1", "u", "b"}, keyboard: true, unrelated: "ab"},
97		{name: "empty flags rejected", fragments: []string{"\x1b[?u"}, unrelated: "\x1b[?u"},
98		{name: "non digit rejected", fragments: []string{"\x1b[?1;2u"}, unrelated: "\x1b[?1;2u"},
99		{name: "no private marker rejected", fragments: []string{"\x1b[1u"}, unrelated: "\x1b[1u"},
100		{name: "truncated flushes", fragments: []string{"\x1b[?1"}, unrelated: "\x1b[?1"},
101	} {
102		t.Run(tc.name, func(t *testing.T) {
103			var p Probe
104			var got []byte
105			for _, f := range tc.fragments {
106				got = append(got, p.Feed([]byte(f))...)
107			}
108			got = append(got, p.Finish()...)
109			require.Equal(t, tc.keyboard, p.KittyKeyboard())
110			require.Equal(t, tc.da1, p.DA1())
111			require.Equal(t, tc.unrelated, string(got))
112		})
113	}
114}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close