1package uidriver
2
3import (
4 "encoding/json"
5 "errors"
6 "strings"
7 "testing"
8
9 "github.com/bnema/vev/internal/ports"
10 "github.com/stretchr/testify/require"
11)
12
13func TestCaptureTextCellsAndOwnedContext(t *testing.T) {
14 snapshot := ports.UISnapshot{Revision: 7, Columns: 4, Rows: 2, Context: ports.UIContext{AttachmentHandle: "a", Generation: 2, Status: ports.UIStatusAttached}, Cells: []ports.UICell{{Text: "界", Width: 2}, {Continuation: true}, {Text: "x", Width: 1}, {Width: 1}, {Width: 1}, {Width: 1}, {Width: 1}, {Width: 1}}}
15 snapshot.Cells[2].Style = ports.UIStyle{Foreground: ports.UIColor{Kind: ports.UIColorRGB, R: 1, G: 2, B: 3}, Background: ports.UIColor{Kind: ports.UIColorIndexed, Index: 4}, Inverse: true}
16 result, err := publicCapture(snapshot, formatBoth)
17 require.NoError(t, err)
18 require.Equal(t, "界x \n ", *result.Text)
19 require.Len(t, result.Cells, 8)
20 require.True(t, result.Cells[1].Continuation)
21 require.Equal(t, colorRGB, result.Cells[2].Style.Foreground.Kind)
22 require.True(t, result.Cells[2].Style.Inverse)
23 result.Cells[2].Text = "changed"
24 require.Equal(t, "x", snapshot.Cells[2].Text)
25 encoded, err := json.Marshal(result)
26 require.NoError(t, err)
27 require.Contains(t, string(encoded), `"attachment":"a"`)
28 require.NotContains(t, string(encoded), "AttachmentHandle")
29}
30
31func TestCaptureRejectsExcessGeometryWithoutResizing(t *testing.T) {
32 _, err := publicCapture(ports.UISnapshot{Columns: maxColumns + 1, Rows: 1}, formatCells)
33 var uiErr *ports.UIError
34 require.ErrorAs(t, err, &uiErr)
35 require.Equal(t, ports.UIErrCaptureTooLarge, uiErr.Code)
36}
37
38func TestCaptureFitsMaximumViewportForStructuredFormats(t *testing.T) {
39 snapshot := ports.UISnapshot{Columns: maxColumns, Rows: maxRows, Cells: make([]ports.UICell, maxColumns*maxRows)}
40 for _, format := range []captureFormat{formatCells, formatBoth} {
41 t.Run(string(format), func(t *testing.T) {
42 require.True(t, captureFitsResponse(snapshot, format))
43 })
44 }
45 capture, err := publicCapture(snapshot, formatCells)
46 require.NoError(t, err)
47 encoded, err := json.Marshal(capture)
48 require.NoError(t, err)
49 require.Less(t, len(encoded), maxResponseBytes)
50}
51
52func TestColorResponseSerializesZeroComponents(t *testing.T) {
53 tests := []struct {
54 name string
55 color colorResponse
56 want string
57 }{
58 {name: "default", color: colorResponse{Kind: colorDefault}, want: `{"kind":"default","index":0,"r":0,"g":0,"b":0}`},
59 {name: "indexed zero", color: colorResponse{Kind: colorIndexed}, want: `{"kind":"indexed","index":0,"r":0,"g":0,"b":0}`},
60 {name: "rgb zero", color: colorResponse{Kind: colorRGB}, want: `{"kind":"rgb","index":0,"r":0,"g":0,"b":0}`},
61 }
62 for _, test := range tests {
63 t.Run(test.name, func(t *testing.T) {
64 encoded, err := json.Marshal(test.color)
65 require.NoError(t, err)
66 require.Equal(t, test.want, string(encoded))
67 })
68 }
69}
70
71func TestCaptureRejectsOversizedCellBeforeSerialization(t *testing.T) {
72 snapshot := ports.UISnapshot{Revision: 1, Columns: 1, Rows: 1, Cells: []ports.UICell{{Text: strings.Repeat("x", maxResponseBytes), Width: 1}}}
73 _, err := publicCapture(snapshot, formatCells)
74 var uiErr *ports.UIError
75 require.ErrorAs(t, err, &uiErr)
76 require.Equal(t, ports.UIErrCaptureTooLarge, uiErr.Code)
77}
78
79func TestErrorResponseNeverExportsUnderlyingMessage(t *testing.T) {
80 response := errorEnvelope(9, errors.New("private viewport or environment"))
81 encoded, err := json.Marshal(response)
82 require.NoError(t, err)
83 require.NotContains(t, string(encoded), "private")
84 require.Equal(t, ports.UIErrUnavailable, response.Error.Code)
85 accepted := errorEnvelope(10, &ports.UIError{Code: ports.UIErrTimeout, Accepted: true, ActionID: 7})
86 require.True(t, accepted.Error.Accepted)
87 require.Equal(t, uint64(7), accepted.Error.ActionID)
88}