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}