result_test.go RawHistoryFolder

1package palette
2
3import (
4	"testing"
5	"time"
6
7	"github.com/bnema/vev/internal/domain"
8	"github.com/bnema/vev/internal/protocol"
9	"github.com/bnema/vev/internal/usecase/command"
10	"github.com/stretchr/testify/require"
11)
12
13func testExactTarget(name string, marker byte) protocol.ExactSessionTarget {
14	return protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{marker}, SessionName: name}
15}
16
17func TestImportedSessionResultKeepsOpaqueKeysAndQualifiedDisplay(t *testing.T) {
18	local := NewImportedSessionResult("local", "aaa/one", "shared", "local", "up", "")
19	remote := NewImportedSessionResult("user@arch", "bbb/shared", "shared", "arch", "up", "")
20
21	require.Equal(t, ResultKindImportedSession, local.Kind())
22	require.Equal(t, "Switch to session shared@local", local.DisplayText())
23	require.Equal(t, "Switch to session shared@arch", remote.DisplayText())
24
25	source, entry, ok := local.ImportedSessionKey()
26	require.True(t, ok)
27	require.Equal(t, "local", source)
28	require.Equal(t, "aaa/one", entry)
29	display, reason, ok := remote.ImportedSessionDisplay()
30	require.True(t, ok)
31	require.Equal(t, "shared@arch", display)
32	require.Empty(t, reason)
33
34	// Homonyms across sources stay distinct: keys never collapse.
35	require.False(t, local.sameTarget(remote))
36	require.True(t, local.sameTarget(NewImportedSessionResult("local", "aaa/one", "renamed", "local", "up", "")))
37
38	// Remote vision tags availability inline: non-live states surface so
39	// stale or broken entries never look attachable. Live rows stay clean.
40	down := NewImportedSessionResult("arch", "aaa/db", "db", "arch", "down", "unreachable")
41	require.Equal(t, "Switch to session db@arch (down: unreachable)", down.DisplayText())
42	broken := NewImportedSessionResult("arch", "aaa/db", "db", "arch", "broken", "")
43	require.Equal(t, "Switch to session db@arch (broken)", broken.DisplayText())
44	stopped := NewImportedSessionResult("local", "aaa/old", "old", "local", "stopped", "")
45	require.Equal(t, "Switch to session old@local (stopped)", stopped.DisplayText())
46
47	// Other kinds expose no imported keys.
48	if _, _, ok := NewCommandResult(command.Command{Code: "NT"}).ImportedSessionKey(); ok {
49		t.Fatal("command results must not expose imported keys")
50	}
51}
52
53func TestResultKindsAndSessionLifecycleTargets(t *testing.T) {
54	created := time.Date(2026, time.March, 1, 2, 3, 4, 0, time.UTC)
55	commandResult := NewCommandResult(command.Command{Code: "NT", Desc: "Create tab"})
56	activeTarget := testExactTarget("work", 1)
57	stoppedTarget := testExactTarget("archive", 2)
58	active := NewActiveSessionResult(activeTarget, created)
59	stopped := NewStoppedSessionResult(stoppedTarget, created)
60	remoteActive := NewActiveSessionResultWithDisplayOrigin(activeTarget, created, "user@remote-host")
61	remoteStopped := NewStoppedSessionResultWithDisplayOrigin(stoppedTarget, created, "user@remote-host")
62
63	require.Equal(t, ResultKindCommand, commandResult.Kind())
64	require.Equal(t, "NT", commandResult.DisplayText())
65	require.Equal(t, "NT", commandResult.SearchText())
66	commandInfo, ok := commandResult.Command()
67	require.True(t, ok)
68	require.Equal(t, "NT", commandInfo.Code)
69
70	require.Equal(t, ResultKindActiveSession, active.Kind())
71	name, ok := active.SessionName()
72	require.True(t, ok)
73	require.Equal(t, "work", name)
74	createdAt, ok := active.SessionCreatedAt()
75	require.True(t, ok)
76	require.Equal(t, created, createdAt)
77	require.Equal(t, "Switch to session work", active.DisplayText())
78	require.Equal(t, "Switch to session work", active.SearchText())
79	target, ok := active.SessionTarget()
80	require.True(t, ok)
81	require.Equal(t, activeTarget, target)
82	require.Equal(t, "Switch to session work@remote-host", remoteActive.DisplayText())
83	name, ok = remoteActive.SessionName()
84	require.True(t, ok)
85	require.Equal(t, "work", name)
86	target, ok = remoteActive.SessionTarget()
87	require.True(t, ok)
88	require.Equal(t, activeTarget, target)
89
90	require.Equal(t, ResultKindStoppedSession, stopped.Kind())
91	name, ok = stopped.SessionName()
92	require.True(t, ok)
93	require.Equal(t, "archive", name)
94	createdAt, ok = stopped.SessionCreatedAt()
95	require.True(t, ok)
96	require.Equal(t, created, createdAt)
97	require.Equal(t, "Resume session archive", stopped.DisplayText())
98	require.Equal(t, "Resume session archive", stopped.SearchText())
99	target, ok = stopped.SessionTarget()
100	require.True(t, ok)
101	require.Equal(t, stoppedTarget, target)
102	require.Equal(t, "Resume session archive@remote-host", remoteStopped.DisplayText())
103	name, ok = remoteStopped.SessionName()
104	require.True(t, ok)
105	require.Equal(t, "archive", name)
106	target, ok = remoteStopped.SessionTarget()
107	require.True(t, ok)
108	require.Equal(t, stoppedTarget, target)
109}
110
111func TestRecentRouteResultCarriesExactNavigationAction(t *testing.T) {
112	action := protocol.RouteNavigationAction{SnapshotGeneration: 4, Key: 7, Generation: 3}
113	result := NewRecentRouteResult("logs", "logs@edge", action)
114
115	require.Equal(t, ResultKindRecentRoute, result.Kind())
116	require.Equal(t, "Switch to session logs@edge", result.DisplayText())
117	require.Equal(t, "Switch to session logs@edge", result.SearchText())
118	got, ok := result.RouteNavigationAction()
119	require.True(t, ok)
120	require.Equal(t, action, got)
121	_, ok = result.SessionName()
122	require.False(t, ok)
123}
124
125func TestSessionResultsSearchSemanticNamesAndDisplayedActionText(t *testing.T) {
126	created := time.Date(2026, time.March, 1, 2, 3, 4, 0, time.UTC)
127	tests := []struct {
128		name      string
129		result    Result
130		query     string
131		positions []int
132	}{
133		{name: "active prefix", result: NewActiveSessionResult(testExactTarget("work", 1), created), query: "switch", positions: []int{0, 1, 2, 3, 4, 5}},
134		{name: "active name", result: NewActiveSessionResult(testExactTarget("work", 1), created), query: "work", positions: []int{18, 19, 20, 21}},
135		{name: "stopped prefix", result: NewStoppedSessionResult(testExactTarget("archive", 2), created), query: "resume", positions: []int{0, 1, 2, 3, 4, 5}},
136		{name: "stopped name", result: NewStoppedSessionResult(testExactTarget("archive", 2), created), query: "archive", positions: []int{15, 16, 17, 18, 19, 20, 21}},
137	}
138
139	for _, tt := range tests {
140		t.Run(tt.name, func(t *testing.T) {
141			matches := Fuzzy([]Result{tt.result}, tt.query)
142			require.Len(t, matches, 1)
143			require.Equal(t, tt.result.DisplayText(), tt.result.SearchText())
144			require.Equal(t, tt.positions, matches[0].Positions)
145		})
146	}
147}

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