v0.30.0 eb3068f

keyboard_test.go RawHistoryFolder

1package dispatcher
2
3import (
4	"context"
5	"fmt"
6	"testing"
7
8	"github.com/bnema/dumber/internal/domain/entity"
9	"github.com/bnema/dumber/internal/ui/coordinator"
10	"github.com/bnema/dumber/internal/ui/input"
11	"github.com/stretchr/testify/assert"
12	"github.com/stretchr/testify/require"
13)
14
15func TestKeyboardDispatcher_TabActionsUseInjectedKeyboardActions(t *testing.T) {
16	ctx := context.Background()
17	called := make([]string, 0)
18	var switchedIndex int
19	d := NewKeyboardDispatcher(
20		ctx,
21		&coordinator.WorkspaceCoordinator{},
22		&coordinator.NavigationCoordinator{},
23		nil,
24		nil,
25		KeyboardActions{
26			NewTab: func(context.Context) error {
27				called = append(called, "new")
28				return nil
29			},
30			CloseTab: func(context.Context) error {
31				called = append(called, "close")
32				return nil
33			},
34			NextTab: func(context.Context) error {
35				called = append(called, "next")
36				return nil
37			},
38			PreviousTab: func(context.Context) error {
39				called = append(called, "previous")
40				return nil
41			},
42			SwitchLastTab: func(context.Context) error {
43				called = append(called, "last")
44				return nil
45			},
46			SwitchTabIndex: func(_ context.Context, index int) error {
47				called = append(called, "index")
48				switchedIndex = index
49				return nil
50			},
51		},
52		func(context.Context) entity.PaneID { return "" },
53	)
54
55	require.NoError(t, d.Dispatch(ctx, input.ActionNewTab))
56	require.NoError(t, d.Dispatch(ctx, input.ActionCloseTab))
57	require.NoError(t, d.Dispatch(ctx, input.ActionNextTab))
58	require.NoError(t, d.Dispatch(ctx, input.ActionPreviousTab))
59	require.NoError(t, d.Dispatch(ctx, input.ActionSwitchLastTab))
60	require.NoError(t, d.Dispatch(ctx, input.ActionSwitchTabIndex5))
61
62	assert.Equal(t, []string{"new", "close", "next", "previous", "last", "index"}, called)
63	assert.Equal(t, 4, switchedIndex)
64}
65
66func TestKeyboardDispatcher_ToggleHistorySidebarCallsCallback(t *testing.T) {
67	ctx := context.Background()
68	d := NewKeyboardDispatcher(ctx, &coordinator.WorkspaceCoordinator{}, &coordinator.NavigationCoordinator{}, nil, nil, KeyboardActions{}, func(context.Context) entity.PaneID { return "" })
69
70	var called bool
71	d.SetOnToggleHistorySidebar(func(context.Context) error {
72		called = true
73		return nil
74	})
75
76	err := d.Dispatch(ctx, input.ActionToggleHistorySystemView)
77	require.NoError(t, err)
78	assert.True(t, called, "onToggleHistorySidebar should have been called")
79}
80
81func TestKeyboardDispatcher_ToggleHistorySystemViewReturnsErrorWhenHandlerMissing(t *testing.T) {
82	ctx := context.Background()
83	d := NewKeyboardDispatcher(ctx, &coordinator.WorkspaceCoordinator{}, &coordinator.NavigationCoordinator{}, nil, nil, KeyboardActions{}, func(context.Context) entity.PaneID { return "" })
84
85	err := d.Dispatch(ctx, input.ActionToggleHistorySystemView)
86	require.Error(t, err)
87	assert.ErrorContains(t, err, "history sidebar unavailable")
88}
89
90func TestKeyboardDispatcher_ToggleHistorySidebarErrorPropagation(t *testing.T) {
91	ctx := context.Background()
92	d := NewKeyboardDispatcher(ctx, &coordinator.WorkspaceCoordinator{}, &coordinator.NavigationCoordinator{}, nil, nil, KeyboardActions{}, func(context.Context) entity.PaneID { return "" })
93
94	wantErr := fmt.Errorf("sidebar toggle failed")
95	d.SetOnToggleHistorySidebar(func(context.Context) error {
96		return wantErr
97	})
98
99	err := d.Dispatch(ctx, input.ActionToggleHistorySystemView)
100	require.Error(t, err)
101	assert.ErrorIs(t, err, wantErr, "onToggleHistorySidebar error should propagate")
102}
103
104func TestKeyboardDispatcher_ToggleHistorySidebarSetThenUnsetReturnsError(t *testing.T) {
105	ctx := context.Background()
106	d := NewKeyboardDispatcher(ctx, &coordinator.WorkspaceCoordinator{}, &coordinator.NavigationCoordinator{}, nil, nil, KeyboardActions{}, func(context.Context) entity.PaneID { return "" })
107
108	d.SetOnToggleHistorySidebar(func(context.Context) error {
109		return nil
110	})
111	d.SetOnToggleHistorySidebar(nil)
112
113	err := d.Dispatch(ctx, input.ActionToggleHistorySystemView)
114	require.Error(t, err)
115	assert.ErrorContains(t, err, "history sidebar unavailable")
116}
117
118func TestKeyboardDispatcher_PassesActivePaneIDToShellCallbacks(t *testing.T) {
119	ctx := context.Background()
120	activePaneID := entity.PaneID("pane-1")
121	d := NewKeyboardDispatcher(ctx, &coordinator.WorkspaceCoordinator{}, &coordinator.NavigationCoordinator{}, nil, nil, KeyboardActions{}, func(context.Context) entity.PaneID {
122		return activePaneID
123	})
124
125	tests := []struct {
126		name   string
127		set    func(func(context.Context, entity.PaneID) error)
128		invoke func(context.Context) error
129	}{
130		{
131			name:   "session open",
132			set:    d.SetOnSessionOpen,
133			invoke: d.handleSessionOpen,
134		},
135		{
136			name:   "move pane to tab",
137			set:    d.SetOnMovePaneToTab,
138			invoke: d.handleMovePaneToTab,
139		},
140		{
141			name:   "move pane to next tab",
142			set:    d.SetOnMovePaneToNextTab,
143			invoke: d.handleMovePaneToNextTab,
144		},
145		{
146			name:   "eject pane to window",
147			set:    d.SetOnEjectPaneToWindow,
148			invoke: d.handleEjectPaneToWindow,
149		},
150	}
151
152	for _, tc := range tests {
153		t.Run(tc.name, func(t *testing.T) {
154			var gotPaneID entity.PaneID
155			tc.set(func(_ context.Context, paneID entity.PaneID) error {
156				gotPaneID = paneID
157				return nil
158			})
159
160			require.NoError(t, tc.invoke(ctx))
161			assert.Equal(t, activePaneID, gotPaneID)
162		})
163	}
164}

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