main ece7682

multi_attachment_callers_test.go RawHistoryFolder

1package daemon
2
3import (
4	"encoding/json"
5	"testing"
6
7	"github.com/bnema/vev/internal/domain"
8	"github.com/bnema/vev/internal/domain/layout"
9	"github.com/stretchr/testify/require"
10)
11
12func TestDaemonActionTargetsUseEachInitiatingAttachmentView(t *testing.T) {
13	sess := registryTestSession()
14	first, second := registryTestAttachment(1), registryTestAttachment(2)
15	require.True(t, sess.registerAttachment(first))
16	require.True(t, sess.registerAttachment(second))
17	require.True(t, sess.selectAttachmentTab(second, domain.TabStableID(sess.tabs[1].stableID)))
18
19	firstTarget := resolveDaemonActionTargetForAttachment(sess, first)
20	secondTarget := resolveDaemonActionTargetForAttachment(sess, second)
21	require.Same(t, sess.tabs[0], firstTarget.tab)
22	require.Same(t, sess.tabs[1], secondTarget.tab)
23	require.Same(t, sess.tabs[0].focusedPane(), firstTarget.pane)
24	require.Same(t, sess.tabs[1].focusedPane(), secondTarget.pane)
25}
26
27func TestAttachmentsKeepDifferentPaneFocusForActionsRenderingAndMetadata(t *testing.T) {
28	sess, firstPane, secondPane := multiPaneAttachmentSession(t)
29	first, second := registryTestAttachment(1), registryTestAttachment(2)
30	require.True(t, sess.registerAttachment(first))
31	require.True(t, sess.registerAttachment(second))
32	require.True(t, sess.updateAttachmentView(second, func(view *attachmentView) {
33		view.paneID = domain.PaneStableID(secondPane.stableID)
34	}))
35
36	firstTarget := resolveDaemonActionTargetForAttachment(sess, first)
37	secondTarget := resolveDaemonActionTargetForAttachment(sess, second)
38	require.Same(t, firstPane, firstTarget.pane)
39	require.Same(t, secondPane, secondTarget.pane)
40
41	firstState, ok := captureLocalRenderState(sess, first, renderCaptureRequest{})
42	require.True(t, ok)
43	secondState, ok := captureLocalRenderState(sess, second, renderCaptureRequest{})
44	require.True(t, ok)
45	require.Equal(t, firstPane.id, firstState.layout.focus)
46	require.Equal(t, secondPane.id, secondState.layout.focus)
47
48	d := newTestDaemon(t, nil, stubClock{})
49	var rows []struct {
50		Pane    string `json:"pane"`
51		Focused bool   `json:"focused"`
52	}
53	output, err := (controlExec{d: d, sess: sess, tab: sess.tabs[0], target: daemonActionTarget{attachment: second}}).ListPanes(true)
54	require.NoError(t, err)
55	require.NoError(t, json.Unmarshal([]byte(output), &rows))
56	for _, row := range rows {
57		require.Equal(t, row.Pane == string(secondPane.id), row.Focused)
58	}
59}
60
61func TestAttachmentFocusActionUpdatesOnlyInitiatingPaneView(t *testing.T) {
62	sess, firstPane, secondPane := multiPaneAttachmentSession(t)
63	first, second := registryTestAttachment(1), registryTestAttachment(2)
64	require.True(t, sess.registerAttachment(first))
65	require.True(t, sess.registerAttachment(second))
66
67	d := newTestDaemon(t, nil, stubClock{})
68	require.NoError(t, d.focusDir(sess, second, layout.Right, nil))
69	require.Equal(t, domain.PaneStableID(firstPane.stableID), first.viewSnapshot().paneID)
70	require.Equal(t, domain.PaneStableID(secondPane.stableID), second.viewSnapshot().paneID)
71}
72
73func TestAttachmentFocusActionCommitsWhenLayoutAlreadyFocusesDestination(t *testing.T) {
74	sess, _, secondPane := multiPaneAttachmentSession(t)
75	ac := registryTestAttachment(1)
76	require.True(t, sess.registerAttachment(ac))
77
78	tb := sess.tabs[0]
79	tb.mu.Lock()
80	tb.tree.Focus = secondPane.id
81	tb.mu.Unlock()
82
83	d := newTestDaemon(t, nil, stubClock{})
84	require.NoError(t, d.focusDir(sess, ac, layout.Right, nil))
85	require.Equal(t, domain.PaneStableID(secondPane.stableID), ac.viewSnapshot().paneID)
86}
87
88func TestPaneRemovalRepairsEachAttachmentViewIndependently(t *testing.T) {
89	sess, firstPane, secondPane := multiPaneAttachmentSession(t)
90	first, second := registryTestAttachment(1), registryTestAttachment(2)
91	require.True(t, sess.registerAttachment(first))
92	require.True(t, sess.registerAttachment(second))
93	require.True(t, sess.updateAttachmentView(second, func(view *attachmentView) {
94		view.paneID = domain.PaneStableID(secondPane.stableID)
95	}))
96
97	require.NoError(t, sess.runMutation(func() error {
98		sess.mu.Lock()
99		defer sess.mu.Unlock()
100		tb := sess.tabs[0]
101		tb.mu.Lock()
102		closeErr := tb.tree.Close(firstPane.id)
103		if closeErr == nil {
104			delete(tb.panes, firstPane.id)
105			tb.bumpLayoutGenerationLocked()
106		}
107		tb.mu.Unlock()
108		if closeErr != nil {
109			return closeErr
110		}
111		sess.repairAttachmentViewsLocked()
112		return nil
113	}))
114
115	require.Equal(t, domain.PaneStableID(secondPane.stableID), first.viewSnapshot().paneID)
116	require.Equal(t, domain.PaneStableID(secondPane.stableID), second.viewSnapshot().paneID)
117}
118
119func multiPaneAttachmentSession(t *testing.T) (*session, *pane, *pane) {
120	t.Helper()
121	sess := registryTestSession()
122	tb := sess.tabs[0]
123	firstPane := tb.panes[layout.PaneID("pane-1")]
124	secondPane := newPaneWithStableID("pane-2", "stable-pane-b", nil, domain.Size{Cols: 80, Rows: 23})
125	tb.mu.Lock()
126	tb.panes[secondPane.id] = secondPane
127	tb.tree = &layout.Tree{
128		Root:  &layout.Node{Kind: layout.Split, Dir: layout.Horizontal, Children: []*layout.Node{layout.NewLeaf(firstPane.id), layout.NewLeaf(secondPane.id)}},
129		Focus: firstPane.id,
130	}
131	tb.bumpLayoutGenerationLocked()
132	tb.mu.Unlock()
133	return sess, firstPane, secondPane
134}

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