v0.3.0 99dc266

navigation_test.go RawHistoryFolder

1package ports
2
3import (
4	"encoding/hex"
5	"testing"
6
7	"github.com/bnema/vev/internal/domain"
8	"github.com/stretchr/testify/require"
9)
10
11func TestNavigationActionWireTable(t *testing.T) {
12	tests := []struct {
13		name    string
14		action  NavigationAction
15		payload []byte
16		valid   bool
17	}{
18		{name: "open home", action: NavigationOpenHomePicker, payload: []byte{1}, valid: true},
19		{name: "back", action: NavigationBack, payload: []byte{2}, valid: true},
20		{name: "zero", action: 0, payload: []byte{0}, valid: false},
21		{name: "unknown", action: 3, payload: []byte{3}, valid: false},
22	}
23
24	for _, tt := range tests {
25		t.Run(tt.name, func(t *testing.T) {
26			got := MarshalNavigationAction(tt.action)
27			if !tt.valid {
28				require.Nil(t, got)
29				_, err := UnmarshalNavigationAction(tt.payload)
30				require.ErrorIs(t, err, ErrInvalidNavigation)
31				return
32			}
33			require.Equal(t, tt.payload, got)
34			decoded, err := UnmarshalNavigationAction(got)
35			require.NoError(t, err)
36			require.Equal(t, tt.action, decoded)
37			assertAllPrefixesFail(t, got, UnmarshalNavigationAction)
38			_, err = UnmarshalNavigationAction(append(append([]byte(nil), got...), 0))
39			require.ErrorIs(t, err, ErrInvalidNavigation)
40		})
41	}
42}
43
44func TestHelloNavigationValidationTable(t *testing.T) {
45	base := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}
46	remoteTarget := &domain.RemoteSessionTarget{
47		Endpoint: "arch", DisplayOrigin: "arch", LifecycleID: domain.SessionLifecycleID{1},
48		SessionName: "work", LiveTabID: "tab-1",
49	}
50	tests := []struct {
51		name        string
52		hello       Hello
53		valid       bool
54		capability  NavigationCapabilities
55		overlay     StartupOverlay
56		wantPayload string
57	}{
58		{name: "ordinary", hello: base, valid: true},
59		{name: "resume route", hello: Hello{Version: ProtocolVersion, Intent: IntentResume, Size: domain.Size{Cols: 80, Rows: 24}}, valid: true},
60		{name: "daemon-owned home picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: true, capability: NavigationCapabilityHomePicker},
61		{name: "remote-target home picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityHomePicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: true, capability: NavigationCapabilityHomePicker},
62		{name: "client-owned home picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker}, valid: false},
63		{name: "daemon-owned back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false},
64		{name: "remote-target back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false},
65		{name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "001f02" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0000" + "0201"},
66		{name: "new rejects navigation", hello: Hello{Version: ProtocolVersion, Intent: IntentNew, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: false},
67		{name: "unknown capability", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: 4}, valid: false},
68		{name: "back without picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack}, valid: false},
69		{name: "picker without back", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, StartupOverlay: StartupOverlaySessionPicker}, valid: false},
70	}
71
72	for _, tt := range tests {
73		t.Run(tt.name, func(t *testing.T) {
74			err := ValidateHello(tt.hello)
75			if tt.valid {
76				require.NoError(t, err)
77				payload := MarshalHello(tt.hello)
78				require.NotNil(t, payload)
79				if tt.wantPayload != "" {
80					expected, decodeErr := hex.DecodeString(tt.wantPayload)
81					require.NoError(t, decodeErr)
82					require.Equal(t, expected, payload)
83					assertAllPrefixesFail(t, expected, UnmarshalHello)
84					assertTrailingGarbageFails(t, expected, UnmarshalHello)
85				}
86				decoded, decodeErr := UnmarshalHello(payload)
87				require.NoError(t, decodeErr)
88				require.Equal(t, tt.capability, decoded.NavigationCapabilities)
89				require.Equal(t, tt.overlay, decoded.StartupOverlay)
90				return
91			}
92			require.Error(t, err)
93			require.Nil(t, MarshalHello(tt.hello))
94		})
95	}
96}

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