1package daemon
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/require"
7
8 renderer "github.com/bnema/vev-vt"
9 "github.com/bnema/vev/internal/domain"
10 scopy "github.com/bnema/vev/internal/usecase/copy"
11 promptui "github.com/bnema/vev/internal/usecase/prompt"
12 "github.com/bnema/vev/internal/usecase/visualsearch"
13)
14
15func TestOverlayRuntimeActive(t *testing.T) {
16 ac := &attachedClient{}
17 ac.initOverlays()
18 require.False(t, ac.overlays.Active())
19
20 ac.overlays.promptMu.Lock()
21 ac.overlays.prompt = promptui.New(" Test ", "")
22 ac.overlays.promptMu.Unlock()
23 require.True(t, ac.overlays.Active())
24}
25
26func TestOverlayRuntimeHandleInputPrecedence(t *testing.T) {
27 cases := []struct {
28 name string
29 setup func(t *testing.T, d *Daemon, sess *session, ac *attachedClient)
30 input []byte
31 check func(t *testing.T, ac *attachedClient)
32 }{
33 {
34 name: "prompt before palette copy",
35 setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) {
36 t.Helper()
37 d.enterCopyMode(sess, ac)
38 d.enterPalette(sess, ac)
39 d.enterPrompt(sess, ac, "Rename", "", func(string) error { return nil })
40 },
41 input: []byte("x"),
42 check: func(t *testing.T, ac *attachedClient) {
43 t.Helper()
44 require.Equal(t, "x", ac.overlays.prompt.Value())
45 require.Equal(t, "", ac.overlays.palette.Query())
46 require.True(t, ac.overlays.copyActive())
47 },
48 },
49 {
50 name: "palette before copy",
51 setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) {
52 t.Helper()
53 d.enterCopyMode(sess, ac)
54 d.enterPalette(sess, ac)
55 },
56 input: []byte("x"),
57 check: func(t *testing.T, ac *attachedClient) {
58 t.Helper()
59 require.Equal(t, "x", ac.overlays.palette.Query())
60 require.True(t, ac.overlays.copyActive())
61 },
62 },
63 {
64 name: "copy",
65 setup: func(t *testing.T, d *Daemon, sess *session, ac *attachedClient) {
66 t.Helper()
67 d.enterCopyMode(sess, ac)
68 },
69 input: []byte("q"),
70 check: func(t *testing.T, ac *attachedClient) {
71 t.Helper()
72 require.False(t, ac.overlays.copyActive())
73 },
74 },
75 }
76
77 for _, tc := range cases {
78 t.Run(tc.name, func(t *testing.T) {
79 p1, releasePTY1 := newBlockingPTY(t)
80 p2, releasePTY2 := newBlockingPTY(t)
81 d, sess, ac, _ := newManualSessionWithPTYs(t, p1, p2)
82 defer releasePTY1()
83 defer releasePTY2()
84 tc.setup(t, d, sess, ac)
85
86 require.True(t, ac.overlays.HandleInput(d, tc.input))
87
88 tc.check(t, ac)
89 })
90 }
91}
92
93func TestOverlayRuntimeSnapshotDoesNotAliasCopySearchState(t *testing.T) {
94 ac := &attachedClient{}
95 ac.initOverlays()
96 snap := scopy.NewSnapshotFromRows([][]renderer.Cell{testRow("alpha"), testRow("beta alpha")}, 16, 2)
97 mode := scopy.NewMode(scopy.NewDocument(snap, domain.DefaultWordSeparators))
98 require.True(t, mode.Search("alpha"))
99 search := visualsearch.New(snap)
100 for _, r := range "alpha" {
101 search.Insert(r)
102 }
103
104 ac.overlays.copyMu.Lock()
105 ac.overlays.copyMode = mode
106 ac.overlays.copySearch = search
107 ac.overlays.copyMu.Unlock()
108
109 renderSnap := ac.overlays.SnapshotForRender()
110 defer renderSnap.Unlock()
111
112 ac.overlays.copyMu.Lock()
113 ac.overlays.copyMode.Searches[0].Row = 99
114 ac.overlays.copySearch.Insert('z')
115 ac.overlays.copyMu.Unlock()
116
117 require.Equal(t, 0, renderSnap.copyMode.Searches[0].Row)
118 require.Equal(t, "alpha", renderSnap.copySearchModel.Query())
119}
120
121func TestOverlayRuntimeHandleInputInactive(t *testing.T) {
122 ac := &attachedClient{}
123 ac.initOverlays()
124
125 require.False(t, ac.overlays.HandleInput(&Daemon{}, []byte("x")))
126}
127
128func TestOverlayRuntimeSnapshotCarriesCopyModeDocumentAndClearReleasesIt(t *testing.T) {
129 ac := &attachedClient{}
130 ac.initOverlays()
131 p := newPane("floating", nil, domain.Size{Cols: 4, Rows: 2})
132 document := scopy.NewDocument(scopy.NewSnapshotFromRows([][]renderer.Cell{testRow("row")}, 4, 2), domain.DefaultWordSeparators)
133
134 ac.overlays.copyMu.Lock()
135 ac.overlays.copyMode = scopy.NewMode(document)
136 ac.overlays.copyPane = p
137 ac.overlays.copyDocument = document
138 ac.overlays.copyMu.Unlock()
139
140 renderSnap := ac.overlays.SnapshotForRender()
141 require.Same(t, p, renderSnap.copyPane)
142 require.NotNil(t, renderSnap.copyMode)
143 require.Same(t, document, renderSnap.copyMode.Document())
144 renderSnap.Unlock()
145
146 ac.overlays.copyMu.Lock()
147 ac.overlays.clearCopyModeLocked()
148 require.Nil(t, ac.overlays.copyPane)
149 require.Nil(t, ac.overlays.copyMode)
150 require.Nil(t, ac.overlays.copyDocument)
151 ac.overlays.copyMu.Unlock()
152}
153
154func TestOverlayRuntimePointerEpochInvalidatesTransferAndPaneClose(t *testing.T) {
155 ac := &attachedClient{}
156 ac.initOverlays()
157 p := newPane("pane", nil, domain.Size{Cols: 4, Rows: 1})
158 document := scopy.NewDocument(scopy.NewSnapshotFromRows([][]renderer.Cell{testRow("row")}, 4, 1), domain.DefaultWordSeparators)
159
160 ac.overlays.copyMu.Lock()
161 ac.overlays.beginCopyPointerLocked(copyPointerState{pane: p, document: document, press: scopy.Pos{}})
162 staleEpoch := ac.overlays.copyPointer.epoch
163 ac.overlays.clearCopyPointerForTransferLocked()
164 ac.overlays.invalidateCopyPointerLocked(true) // release/close while publish revalidates
165 require.NotEqual(t, staleEpoch, ac.overlays.copyPointerEpoch)
166 require.False(t, ac.overlays.copyPointer.valid)
167 ac.overlays.copyCandidate = scopy.NewMode(document)
168 ac.overlays.copyDocument = document
169 ac.overlays.copyPane = p
170 ac.overlays.copyMu.Unlock()
171
172 require.True(t, ac.overlays.clearCopyModeForPane(p))
173 ac.overlays.copyMu.Lock()
174 require.False(t, ac.overlays.copyPointer.valid)
175 ac.overlays.copyMu.Unlock()
176}
177
178func TestOverlayRuntimeCopyModeDocumentSurvivesRingOverwrite(t *testing.T) {
179 ac := &attachedClient{}
180 ac.initOverlays()
181 history := newTestHistory(1)
182 appendHistoryRow(t, history, testRow("before"))
183 document := scopy.NewDocument(scopy.NewSnapshot(history, renderer.NewFrame(6, 1), nil, nil), domain.DefaultWordSeparators)
184
185 ac.overlays.copyMu.Lock()
186 ac.overlays.copyMode = scopy.NewMode(document)
187 ac.overlays.copyDocument = document
188 ac.overlays.copyMu.Unlock()
189
190 appendHistoryRow(t, history, testRow("after"))
191 renderSnap := ac.overlays.SnapshotForRender()
192 defer renderSnap.Unlock()
193 require.NotNil(t, renderSnap.copyMode)
194 require.Same(t, document, renderSnap.copyMode.Document())
195 require.Equal(t, "before", rowText(renderSnap.copyMode.Document().Snapshot().Row(0)))
196}