v0.29.0 21b3150

keyboard_test.go RawHistoryFolder

1package dispatcher
2
3import (
4	"context"
5	"testing"
6
7	"github.com/bnema/dumber/internal/application/usecase"
8	"github.com/bnema/dumber/internal/domain/entity"
9	"github.com/bnema/dumber/internal/ui/component"
10	"github.com/bnema/dumber/internal/ui/coordinator"
11	"github.com/bnema/dumber/internal/ui/input"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/require"
14)
15
16func TestKeyboardDispatcher_TabActionsUseInjectedKeyboardActions(t *testing.T) {
17	ctx := context.Background()
18	called := make([]string, 0)
19	var switchedIndex int
20	d := NewKeyboardDispatcher(
21		ctx,
22		&coordinator.WorkspaceCoordinator{},
23		&coordinator.NavigationCoordinator{},
24		nil,
25		nil,
26		KeyboardActions{
27			NewTab: func(context.Context) error {
28				called = append(called, "new")
29				return nil
30			},
31			CloseTab: func(context.Context) error {
32				called = append(called, "close")
33				return nil
34			},
35			NextTab: func(context.Context) error {
36				called = append(called, "next")
37				return nil
38			},
39			PreviousTab: func(context.Context) error {
40				called = append(called, "previous")
41				return nil
42			},
43			SwitchLastTab: func(context.Context) error {
44				called = append(called, "last")
45				return nil
46			},
47			SwitchTabIndex: func(_ context.Context, index int) error {
48				called = append(called, "index")
49				switchedIndex = index
50				return nil
51			},
52		},
53		func(context.Context) entity.PaneID { return "" },
54	)
55
56	require.NoError(t, d.Dispatch(ctx, input.ActionNewTab))
57	require.NoError(t, d.Dispatch(ctx, input.ActionCloseTab))
58	require.NoError(t, d.Dispatch(ctx, input.ActionNextTab))
59	require.NoError(t, d.Dispatch(ctx, input.ActionPreviousTab))
60	require.NoError(t, d.Dispatch(ctx, input.ActionSwitchLastTab))
61	require.NoError(t, d.Dispatch(ctx, input.ActionSwitchTabIndex5))
62
63	assert.Equal(t, []string{"new", "close", "next", "previous", "last", "index"}, called)
64	assert.Equal(t, 4, switchedIndex)
65}
66
67func TestKeyboardDispatcher_ToggleHistorySystemViewOpensRightSplit(t *testing.T) {
68	ctx := context.Background()
69	ids := []string{"pane-2", "split-1"}
70	idx := 0
71	panesUC := usecase.NewManagePanesUseCase(func() string {
72		id := ids[idx]
73		idx++
74		return id
75	})
76
77	initialPane := entity.NewPane("pane-1")
78	initialPane.URI = "https://example.com"
79	ws := entity.NewWorkspace("ws-1", initialPane)
80	wsCoord := coordinator.NewWorkspaceCoordinator(ctx, coordinator.WorkspaceCoordinatorConfig{
81		PanesUC: panesUC,
82		GetActiveWS: func() (*entity.Workspace, *component.WorkspaceView) {
83			return ws, nil
84		},
85	})
86
87	d := NewKeyboardDispatcher(ctx, wsCoord, &coordinator.NavigationCoordinator{}, nil, nil, KeyboardActions{}, func(context.Context) entity.PaneID { return "" })
88
89	err := d.Dispatch(ctx, input.ActionToggleHistorySystemView)
90	require.NoError(t, err)
91
92	require.Equal(t, 2, ws.PaneCount())
93	active := ws.ActivePane()
94	require.NotNil(t, active)
95	require.NotNil(t, active.Pane)
96	assert.Equal(t, entity.PaneID("pane-2"), active.Pane.ID)
97	assert.Equal(t, "dumb://history", active.Pane.URI)
98}
99
100func TestKeyboardDispatcher_PassesActivePaneIDToShellCallbacks(t *testing.T) {
101	ctx := context.Background()
102	activePaneID := entity.PaneID("pane-1")
103	d := NewKeyboardDispatcher(ctx, &coordinator.WorkspaceCoordinator{}, &coordinator.NavigationCoordinator{}, nil, nil, KeyboardActions{}, func(context.Context) entity.PaneID {
104		return activePaneID
105	})
106
107	tests := []struct {
108		name   string
109		set    func(func(context.Context, entity.PaneID) error)
110		invoke func(context.Context) error
111	}{
112		{
113			name:   "session open",
114			set:    d.SetOnSessionOpen,
115			invoke: d.handleSessionOpen,
116		},
117		{
118			name:   "move pane to tab",
119			set:    d.SetOnMovePaneToTab,
120			invoke: d.handleMovePaneToTab,
121		},
122		{
123			name:   "move pane to next tab",
124			set:    d.SetOnMovePaneToNextTab,
125			invoke: d.handleMovePaneToNextTab,
126		},
127	}
128
129	for _, tc := range tests {
130		t.Run(tc.name, func(t *testing.T) {
131			var gotPaneID entity.PaneID
132			tc.set(func(_ context.Context, paneID entity.PaneID) error {
133				gotPaneID = paneID
134				return nil
135			})
136
137			require.NoError(t, tc.invoke(ctx))
138			assert.Equal(t, activePaneID, gotPaneID)
139		})
140	}
141}

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 8

Tags 42

↑↓ moveenter openesc close