1package daemon
2
3import (
4 "testing"
5
6 "github.com/bnema/vev/internal/domain"
7 "github.com/bnema/vev/internal/protocol"
8 "github.com/stretchr/testify/require"
9)
10
11func TestRenderPublicationUsesCapturedIdentityAndRevision(t *testing.T) {
12 for _, scenario := range []string{"unchanged", "rename after capture", "view replaced after capture"} {
13 t.Run(scenario, func(t *testing.T) {
14 d, sess, ac, sends := newManualSessionWithPTYs(t, nil)
15 ac.sendMu.Lock()
16 state, ok := captureRenderState(sess, ac, renderCaptureRequest{reset: true})
17 if !ok {
18 ac.sendMu.Unlock()
19 t.Fatal("capture rejected")
20 }
21 want := state.viewContext()
22 want.Publication = 1
23 require.NoError(t, want.Validate())
24 composed := composeFrame(*state, ac.render.cache)
25 switch scenario {
26 case "rename after capture":
27 sess.mu.Lock()
28 sess.name = "renamed"
29 sess.mu.Unlock()
30 case "view replaced after capture":
31 view := ac.viewSnapshot()
32 view.revision++
33 ac.publishView(view)
34 }
35 require.True(t, d.publishCapturedFrameForTest(sess, ac, state, composed)) // releases sendMu
36 if scenario == "view replaced after capture" {
37 require.Empty(t, drainAllFrames(sends))
38 require.Zero(t, ac.output.viewPublication)
39 require.Zero(t, ac.output.next)
40 return
41 }
42 output := unmarshalTestOutput(t, awaitFrame(t, sends, "Output").Payload)
43 require.Equal(t, &want, output.Context)
44 require.Equal(t, state.view.revision, output.ViewRevision)
45 require.Equal(t, want, ac.output.lastViewContext)
46 require.Equal(t, domain.PaneStableID(sess.tabs[0].focusedPane().stableID), output.Context.FocusedPaneID)
47 })
48 }
49}
50
51func TestRenderPublishesContextWithoutTerminalBytes(t *testing.T) {
52 d, sess, ac, sends := newManualSessionWithPTYs(t, nil)
53 paint := func(reset bool) {
54 t.Helper()
55 ac.sendMu.Lock()
56 state, ok := captureRenderState(sess, ac, renderCaptureRequest{reset: reset})
57 if !ok {
58 ac.sendMu.Unlock()
59 t.Fatal("capture rejected")
60 }
61 composed := composeFrame(*state, ac.render.cache)
62 require.True(t, d.publishCapturedFrameForTest(sess, ac, state, composed))
63 }
64 paint(true)
65 initial := unmarshalTestOutput(t, awaitFrame(t, sends, "Output").Payload)
66 require.NotNil(t, initial.Context)
67 beforeOutstanding := ac.output.outstanding()
68 sess.mu.Lock()
69 sess.name = "renamed"
70 sess.mu.Unlock()
71 // The test's bars contain no session name, so only semantic context changes.
72 paint(false)
73 update := decodeServerMessage(t, awaitFrame(t, sends, "UIViewUpdate")).(protocol.UIViewUpdate)
74 require.Equal(t, initial.Epoch, update.Epoch)
75 require.Equal(t, initial.New, update.State)
76 require.Equal(t, initial.Context.Publication+1, update.Context.Publication)
77 require.Equal(t, "renamed", update.Context.Route.Target.SessionName)
78 require.Equal(t, initial.Context.Route.Target.LifecycleID, update.Context.Route.Target.LifecycleID)
79 position := decodeServerMessage(t, awaitFrame(t, sends, "RoutePosition")).(protocol.RoutePosition)
80 require.Equal(t, update.Context.Route.Target, position.Target)
81 require.Equal(t, beforeOutstanding, ac.output.outstanding(), "metadata does not create an ACK obligation")
82 require.Empty(t, drainAllFrames(sends))
83 paint(false)
84 require.Empty(t, drainAllFrames(sends), "unchanged view without a fence sends nothing")
85 require.Equal(t, update.Context.Publication, ac.output.viewPublication)
86 query, err := ac.output.sideEffect([]byte("query"), 0)
87 require.NoError(t, err)
88 require.Nil(t, query.Context)
89 require.Equal(t, update.Context, ac.output.lastViewContext)
90}