v0.3.0 99dc266

transport_replay_integration_test.go RawHistoryFolder

1package main_test
2
3import (
4	"io"
5	"net"
6	"sync"
7	"testing"
8
9	vt "github.com/bnema/vev-vt"
10	renderer "github.com/bnema/vev-vt/ansi"
11	"github.com/bnema/vev/internal/adapters/ipc"
12	"github.com/bnema/vev/internal/adapters/sshstdio"
13	"github.com/bnema/vev/internal/ports"
14	"github.com/bnema/vev/internal/testutil/replaytest"
15	"github.com/stretchr/testify/require"
16)
17
18// TestTransportReplayIntegration stays at the legal application boundary: it
19// observes only typed output frames and terminal bytes, never daemon internals.
20func TestTransportReplayIntegration(t *testing.T) {
21	frames := replaytest.Transcript()
22	left, right := net.Pipe()
23	t.Cleanup(func() { _ = left.Close(); _ = right.Close() })
24	sender, receiver := ipc.NewTransport(left), ipc.NewTransport(right)
25	var wg sync.WaitGroup
26	wg.Go(func() {
27		for _, frame := range frames {
28			if err := sender.Send(frame); err != nil {
29				t.Errorf("Send: %v", err)
30				return
31			}
32		}
33	})
34
35	terminal := vt.NewScreen(8, 3)
36	var bytes []byte
37	for _, want := range frames {
38		got, err := receiver.Recv()
39		require.NoError(t, err)
40		require.Equal(t, want, got)
41		output, err := ports.UnmarshalOutput(got.Payload)
42		require.NoError(t, err)
43		bytes = append(bytes, output.Data...)
44		terminal.Write(output.Data)
45	}
46	wg.Wait()
47	require.Equal(t, "\x1b[2J\x1b[Hone\r\ntwo\x1b[2;1HTWO", string(bytes))
48	require.Equal(t, []string{"one     ", "TWO     ", "        "}, integrationFrameRows(terminal.Frame))
49}
50
51// TestThemeGenerationTransportSequences proves the generation's cleared then
52// definitive wire sequence is unchanged across the local socket and remote
53// SSH-stdio transports. The transports only carry frames; neither can infer
54// or alter the palette lifecycle.
55func TestThemeGenerationTransportSequences(t *testing.T) {
56	cleared := ports.Theme{
57		HasForeground: true, Foreground: renderer.RGB{R: 1, G: 2, B: 3},
58		HasBackground: true, Background: renderer.RGB{R: 4, G: 5, B: 6},
59		TrueColor: true, SchemeKnown: true,
60	}
61	palette := [16]renderer.RGB{2: {R: 125, G: 181, B: 181}, 10: {R: 125, G: 181, B: 181}}
62	definitive := cleared
63	definitive.Palette = palette
64	definitive.PaletteKnown = 1<<2 | 1<<10
65	sequence := []ports.Theme{cleared, definitive}
66
67	tests := []struct {
68		name string
69		pair func(*testing.T) (ports.Transport, ports.Transport)
70	}{
71		{
72			name: "local ipc socket",
73			pair: func(t *testing.T) (ports.Transport, ports.Transport) {
74				left, right := net.Pipe()
75				t.Cleanup(func() { _ = left.Close(); _ = right.Close() })
76				return ipc.NewTransport(left), ipc.NewTransport(right)
77			},
78		},
79		{
80			name: "remote ssh stdio",
81			pair: func(t *testing.T) (ports.Transport, ports.Transport) {
82				clientRead, serverWrite := io.Pipe()
83				serverRead, clientWrite := io.Pipe()
84				closeClient := func() error {
85					_ = clientRead.Close()
86					return clientWrite.Close()
87				}
88				closeServer := func() error {
89					_ = serverRead.Close()
90					return serverWrite.Close()
91				}
92				t.Cleanup(func() { _ = closeClient(); _ = closeServer() })
93				return sshstdio.NewTransport(clientRead, clientWrite, closeClient), sshstdio.NewTransport(serverRead, serverWrite, closeServer)
94			},
95		},
96	}
97
98	for _, tt := range tests {
99		t.Run(tt.name, func(t *testing.T) {
100			sender, receiver := tt.pair(t)
101			done := make(chan error, 1)
102			go func() {
103				for _, snapshot := range sequence {
104					if err := sender.Send(ports.Frame{Type: ports.MsgTheme, Payload: ports.MarshalTheme(snapshot)}); err != nil {
105						done <- err
106						return
107					}
108				}
109				done <- nil
110			}()
111
112			for _, want := range sequence {
113				frame, err := receiver.Recv()
114				require.NoError(t, err)
115				require.Equal(t, ports.MsgTheme, frame.Type)
116				require.Equal(t, ports.MarshalTheme(want), frame.Payload, "theme payload must be preserved byte-for-byte before decode")
117				got, err := ports.UnmarshalTheme(frame.Payload)
118				require.NoError(t, err)
119				require.Equal(t, want, got)
120			}
121			require.NoError(t, <-done)
122		})
123	}
124}
125
126func integrationFrameRows(frame renderer.Frame) []string {
127	rows := make([]string, frame.Height)
128	for y := range rows {
129		cells := frame.Row(y)
130		for _, cell := range cells {
131			rows[y] += string(cell.Rune)
132		}
133	}
134	return rows
135}

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