v0.3.0 99dc266

remote_preview_capture_test.go RawHistoryFolder

1package daemon
2
3import (
4	"testing"
5
6	"github.com/stretchr/testify/require"
7
8	vt "github.com/bnema/vev-vt"
9	renderer "github.com/bnema/vev-vt/ansi"
10	"github.com/bnema/vev/internal/domain"
11	"github.com/bnema/vev/internal/ports"
12)
13
14func TestCaptureRemotePreviewUsesBottomRowsForShorterPreview(t *testing.T) {
15	d := newTestDaemon(t, nil, stubClock{})
16	sess := addControlSession(d, "work", "tab-1", "pane-1")
17	sess.ephemeral = false
18	sess.incarnation = remoteLifecycleForTest()
19	pane := sess.tabs[0].focusedPane()
20	pane.mu.Lock()
21	pane.screen = vt.NewScreen(2, 3)
22	pane.screen.Frame.Set(0, 0, renderer.Cell{Rune: 'a'})
23	pane.screen.Frame.Set(0, 1, renderer.Cell{Rune: 'b'})
24	pane.screen.Frame.Set(0, 2, renderer.Cell{Rune: 'c'})
25	pane.mu.Unlock()
26
27	target := domain.RemoteSessionTarget{
28		Endpoint: "arch", DisplayOrigin: "arch", LifecycleID: sess.incarnation,
29		SessionName: "work", LiveTabID: "tab-1",
30	}
31	preview, err := d.captureRemotePreview(ports.RemotePreviewRequest{
32		Version: ports.RemotePreviewSchemaVersion, Target: target, Width: 1, Height: 2,
33	})
34	require.NoError(t, err)
35	require.Equal(t, []rune{'b', 'c'}, []rune{preview.Cells[0].Rune, preview.Cells[1].Rune})
36}
37
38func TestCaptureRemotePreviewCompactsBlankTail(t *testing.T) {
39	type screenCell struct {
40		x, y int
41		cell renderer.Cell
42	}
43	type previewCell struct {
44		index int
45		rune  rune
46		style *renderer.Style
47	}
48	styledBlank := renderer.DefaultStyle()
49	styledBlank.Background = 4
50	tests := []struct {
51		name                      string
52		screenWidth, screenHeight int
53		cells                     []screenCell
54		width, height             uint16
55		wantHeight                uint16
56		wantCells                 []previewCell
57	}{
58		{
59			name:         "keeps short screen content visible",
60			screenWidth:  2,
61			screenHeight: 4,
62			cells:        []screenCell{{x: 0, y: 0, cell: renderer.Cell{Rune: 'v'}}},
63			width:        2,
64			height:       2,
65			wantHeight:   1,
66			wantCells:    []previewCell{{index: 0, rune: 'v'}, {index: 1, rune: ' '}},
67		},
68		{
69			name:         "ignores boundary split wide rune",
70			screenWidth:  3,
71			screenHeight: 3,
72			cells: []screenCell{
73				{x: 0, y: 1, cell: renderer.Cell{Rune: 'a'}},
74				{x: 1, y: 2, cell: renderer.Cell{Rune: '界'}},
75				{x: 2, y: 2, cell: renderer.Cell{Continuation: true}},
76			},
77			width:      2,
78			height:     2,
79			wantHeight: 1,
80			wantCells:  []previewCell{{index: 0, rune: 'a'}, {index: 1, rune: ' '}},
81		},
82		{
83			name:         "preserves styled blank tail",
84			screenWidth:  2,
85			screenHeight: 4,
86			cells: []screenCell{
87				{x: 0, y: 2, cell: renderer.Cell{Rune: 'a'}},
88				{x: 0, y: 3, cell: renderer.Cell{Rune: ' ', Style: styledBlank}},
89			},
90			width:      2,
91			height:     2,
92			wantHeight: 2,
93			wantCells:  []previewCell{{index: 0, rune: 'a'}, {index: 2, rune: ' ', style: &styledBlank}},
94		},
95	}
96
97	for _, test := range tests {
98		t.Run(test.name, func(t *testing.T) {
99			d := newTestDaemon(t, nil, stubClock{})
100			sess := addControlSession(d, "work", "tab-1", "pane-1")
101			sess.ephemeral = false
102			sess.incarnation = remoteLifecycleForTest()
103			pane := sess.tabs[0].focusedPane()
104			pane.mu.Lock()
105			pane.screen = vt.NewScreen(test.screenWidth, test.screenHeight)
106			for _, cell := range test.cells {
107				pane.screen.Frame.Set(cell.x, cell.y, cell.cell)
108			}
109			pane.mu.Unlock()
110
111			target := domain.RemoteSessionTarget{
112				Endpoint: "arch", DisplayOrigin: "arch", LifecycleID: sess.incarnation,
113				SessionName: "work", LiveTabID: "tab-1",
114			}
115			preview, err := d.captureRemotePreview(ports.RemotePreviewRequest{
116				Version: ports.RemotePreviewSchemaVersion, Target: target, Width: test.width, Height: test.height,
117			})
118			require.NoError(t, err)
119			require.Equal(t, test.wantHeight, preview.Height)
120			for _, want := range test.wantCells {
121				require.Equal(t, want.rune, preview.Cells[want.index].Rune)
122				if want.style != nil {
123					require.True(t, preview.Cells[want.index].Style.Equal(*want.style))
124				}
125			}
126		})
127	}
128}
129
130func TestStaticRemotePickerPreviewUsesOnlyItsTextRow(t *testing.T) {
131	preview := staticRemotePickerPreview(8, 4, "loading")
132
133	require.Equal(t, 8, preview.Width)
134	require.Equal(t, 1, preview.Height)
135	require.Equal(t, []rune{'l', 'o', 'a', 'd', 'i', 'n', 'g', ' '}, []rune{
136		preview.Rows[0][0].Rune, preview.Rows[0][1].Rune, preview.Rows[0][2].Rune, preview.Rows[0][3].Rune,
137		preview.Rows[0][4].Rune, preview.Rows[0][5].Rune, preview.Rows[0][6].Rune, preview.Rows[0][7].Rune,
138	})
139}
140
141func TestCaptureRemotePreviewDoesNotSplitWideRuneAtCropBoundary(t *testing.T) {
142	d := newTestDaemon(t, nil, stubClock{})
143	sess := addControlSession(d, "work", "tab-1", "pane-1")
144	sess.ephemeral = false
145	sess.incarnation = remoteLifecycleForTest()
146	pane := sess.tabs[0].focusedPane()
147	pane.mu.Lock()
148	pane.screen = vt.NewScreen(3, 1)
149	style := renderer.DefaultStyle()
150	style.Bold = true
151	pane.screen.Frame.Set(1, 0, renderer.Cell{Rune: '界', Style: style})
152	pane.screen.Frame.Set(2, 0, renderer.Cell{Continuation: true, Style: style})
153	pane.mu.Unlock()
154
155	target := domain.RemoteSessionTarget{
156		Endpoint: "arch", DisplayOrigin: "arch", LifecycleID: sess.incarnation,
157		SessionName: "work", LiveTabID: "tab-1",
158	}
159	preview, err := d.captureRemotePreview(ports.RemotePreviewRequest{
160		Version: ports.RemotePreviewSchemaVersion, Target: target, Width: 2, Height: 1,
161	})
162	require.NoError(t, err)
163	require.Equal(t, uint16(2), preview.Width)
164	require.Equal(t, rune(' '), preview.Cells[1].Rune, "a crop cannot expose a dangling wide rune")
165	require.False(t, preview.Cells[1].Continuation)
166	require.True(t, preview.Cells[1].Style.Equal(style), "boundary replacement preserves the cell style")
167	require.NoError(t, ports.ValidateRemotePreview(preview))
168}

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