response_test.go RawHistoryFolder

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}

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