detached ece7682

request_test.go RawHistoryFolder

1package uidriver
2
3import (
4	"strings"
5	"testing"
6	"time"
7
8	"github.com/bnema/vev/internal/ports"
9	"github.com/stretchr/testify/require"
10)
11
12func TestDecodeRequestStrictOperationSchemas(t *testing.T) {
13	for _, tt := range []struct {
14		name, input string
15		code        ports.UIErrorCode
16	}{
17		{"capture", `{"version":1,"id":1,"op":"capture","attachment":"a"}`, ""},
18		{"keys", `{"version":1,"id":1,"op":"keys","attachment":"a","generation":2,"keys":["Alt+Space"],"timeout_ms":30}`, ""},
19		{"text", `{"version":1,"id":1,"op":"text","attachment":"a","generation":2,"text":"hello"}`, ""},
20		{"wait", `{"version":1,"id":1,"op":"wait","attachment":"a","after_action":3,"expect":{"text_contains":"hello","status":"attached","focus":{"tab_id":"t","pane_id":"p"},"session":{"lifecycle_id":"01000000000000000000000000000000","session_name":"work"}}}`, ""},
21		{"unknown", `{"version":1,"id":1,"op":"capture","attachment":"a","extra":0}`, ports.UIErrInvalidRequest},
22		{"wrong case", `{"Version":1,"id":1,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
23		{"duplicate", `{"version":1,"id":1,"id":2,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
24		{"escaped duplicate", `{"version":1,"id":1,"\u0069d":2,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
25		{"nested duplicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"attached","status":"detached"}}`, ports.UIErrInvalidRequest},
26		{"picker predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"picker"}}`, ""},
27		{"connecting predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"connecting"}}`, ""},
28		{"removed detached predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"detached"}}`, ports.UIErrInvalidRequest},
29		{"removed reconnecting predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"reconnecting"}}`, ports.UIErrInvalidRequest},
30		{"removed transitioning predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"transitioning"}}`, ports.UIErrInvalidRequest},
31		{"unknown status predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"status":"terminating"}}`, ports.UIErrInvalidRequest},
32		{"nested unknown", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"focus":{"tab_id":"t","pane_id":"p","extra":"x"}}}`, ports.UIErrInvalidRequest},
33		{"unknown predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{"regex":".*"}}`, ports.UIErrInvalidRequest},
34		{"empty predicate", `{"version":1,"id":1,"op":"wait","attachment":"a","expect":{}}`, ports.UIErrInvalidRequest},
35		{"null option", `{"version":1,"id":1,"op":"capture","attachment":"a","format":null}`, ports.UIErrInvalidRequest},
36		{"zero id", `{"version":1,"id":0,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
37		{"negative id", `{"version":1,"id":-1,"op":"capture","attachment":"a"}`, ports.UIErrInvalidRequest},
38		{"wrong version", `{"version":2,"id":1,"op":"capture","attachment":"a"}`, ports.UIErrUnsupportedVersion},
39		{"mixed operation", `{"version":1,"id":1,"op":"keys","attachment":"a","generation":2,"keys":["x"],"text":"x"}`, ports.UIErrInvalidRequest},
40		{"capture generation", `{"version":1,"id":1,"op":"capture","attachment":"a","generation":2}`, ports.UIErrInvalidRequest},
41		{"zero generation", `{"version":1,"id":1,"op":"text","attachment":"a","generation":0,"text":"x"}`, ports.UIErrInvalidRequest},
42		{"timeout bound", `{"version":1,"id":1,"op":"text","attachment":"a","generation":2,"text":"x","timeout_ms":30001}`, ports.UIErrInvalidRequest},
43		{"truncated", `{"version":1`, ports.UIErrInvalidRequest},
44		{"trailing", `{"version":1,"id":1,"op":"capture","attachment":"a"}{}`, ports.UIErrInvalidRequest},
45		{"oversized", strings.Repeat(" ", maxRequestBytes+1), ports.UIErrInvalidRequest},
46	} {
47		t.Run(tt.name, func(t *testing.T) {
48			decoded, err := decodeRequest([]byte(tt.input))
49			if tt.code == "" {
50				require.NoError(t, err)
51				require.Equal(t, uint64(1), decoded.ID)
52			} else {
53				var uiErr *ports.UIError
54				require.ErrorAs(t, err, &uiErr)
55				require.Equal(t, tt.code, uiErr.Code)
56			}
57		})
58	}
59}
60
61func TestDecodeRequestDefaultsAndTypedValues(t *testing.T) {
62	capture, err := decodeRequest([]byte(`{"version":1,"id":1,"op":"capture","attachment":"a"}`))
63	require.NoError(t, err)
64	require.Equal(t, formatText, capture.Format)
65	keys, err := decodeRequest([]byte(`{"version":1,"id":2,"op":"keys","attachment":"a","generation":4,"keys":["Escape"],"timeout_ms":25}`))
66	require.NoError(t, err)
67	require.Equal(t, 25*time.Millisecond, keys.Action.Timeout)
68	require.Equal(t, uint64(4), keys.Action.Generation)
69}

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 12

Tags 11

↑↓ moveenter openesc close