1package config
2
3import (
4 "context"
5 "path/filepath"
6 "sync"
7 "testing"
8
9 "github.com/bnema/dumber/internal/application/port"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12)
13
14// newTestKeybindingsManager creates a Manager with a temp config dir.
15func newTestKeybindingsManager(t *testing.T) *Manager {
16 t.Helper()
17 configHome := t.TempDir()
18
19 t.Setenv("XDG_CONFIG_HOME", configHome)
20 t.Setenv("XDG_DATA_HOME", filepath.Join(configHome, "data"))
21 t.Setenv("XDG_STATE_HOME", filepath.Join(configHome, "state"))
22 t.Setenv("XDG_CACHE_HOME", filepath.Join(configHome, "cache"))
23
24 mgr, err := NewManager()
25 require.NoError(t, err)
26 require.NoError(t, mgr.Load())
27 return mgr
28}
29
30// inMemoryManager creates a lightweight Manager without file I/O for tests
31// that only query config data without saving.
32func inMemoryManager() *Manager {
33 return &Manager{
34 config: DefaultConfig(),
35 mu: sync.RWMutex{},
36 }
37}
38
39func TestKeybindingsGateway_VimModeIncludedInGetKeybindings(t *testing.T) {
40 gw := NewKeybindingsGateway(inMemoryManager())
41 cfg, err := gw.GetKeybindings(context.Background())
42 require.NoError(t, err)
43
44 var pageGroup *port.KeybindingGroup
45 for i, g := range cfg.Groups {
46 if g.Mode == "vim" {
47 pageGroup = &cfg.Groups[i]
48 break
49 }
50 }
51 require.NotNil(t, pageGroup, "vim mode group must be present in GetKeybindings output")
52 assert.Equal(t, "Vim Mode", pageGroup.DisplayName)
53 assert.Equal(t, "ctrl+y", pageGroup.Activation)
54
55 // Verify all expected actions are present.
56 expectedActions := map[string]struct{}{
57 "vim-scroll-left": {},
58 "vim-scroll-down": {},
59 "vim-scroll-up": {},
60 "vim-scroll-right": {},
61 "vim-scroll-down-fast": {},
62 "vim-scroll-up-fast": {},
63 "confirm": {},
64 "cancel": {},
65 }
66
67 for _, entry := range pageGroup.Bindings {
68 delete(expectedActions, entry.Action)
69 assert.NotEmpty(t, entry.Keys, "action %q has empty keys", entry.Action)
70 assert.False(t, entry.IsCustom, "default vim mode config should not have IsCustom=true")
71 }
72 assert.Empty(t, expectedActions, "vim mode group missing actions: %v", expectedActions)
73
74 // Must be placed after global, pane, tab groups (consistent ordering).
75 groups := cfg.Groups
76 foundIdx := -1
77 for i, g := range groups {
78 if g.Mode == "vim" {
79 foundIdx = i
80 break
81 }
82 }
83 assert.Greater(t, foundIdx, 2, "vim mode group should be after pane/tab groups")
84}
85
86func TestKeybindingsGateway_VimModeIncludedInGetDefaultKeybindings(t *testing.T) {
87 gw := NewKeybindingsGateway(inMemoryManager())
88 cfg, err := gw.GetDefaultKeybindings(context.Background())
89 require.NoError(t, err)
90
91 var pageGroup *port.KeybindingGroup
92 for i, g := range cfg.Groups {
93 if g.Mode == "vim" {
94 pageGroup = &cfg.Groups[i]
95 break
96 }
97 }
98 require.NotNil(t, pageGroup, "vim mode group must be present in GetDefaultKeybindings output")
99 assert.Equal(t, "Vim Mode", pageGroup.DisplayName)
100 assert.Equal(t, "ctrl+y", pageGroup.Activation)
101
102 // Verify default keys match actual keys.
103 for _, entry := range pageGroup.Bindings {
104 assert.Equal(t, entry.DefaultKeys, entry.Keys,
105 "default vim mode action %q should have keys == default_keys", entry.Action)
106 }
107}
108
109func TestKeybindingsGateway_VimModeSetKeybindingUpdatesFile(t *testing.T) {
110 mgr := newTestKeybindingsManager(t)
111 gw := NewKeybindingsGateway(mgr)
112
113 err := gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{
114 Mode: "vim",
115 Action: "vim-scroll-left",
116 Keys: []string{"a"},
117 })
118 require.NoError(t, err)
119
120 // Reload and assert on the parsed Vim-mode configuration rather than raw TOML text.
121 reloaded, reloadErr := NewManager()
122 require.NoError(t, reloadErr)
123 require.NoError(t, reloaded.Load())
124 assert.Equal(t, []string{"a"}, reloaded.Get().Workspace.VimMode.Actions["vim-scroll-left"].Keys)
125}
126
127func TestKeybindingsGateway_VimModeResetKeybinding(t *testing.T) {
128 mgr := newTestKeybindingsManager(t)
129 gw := NewKeybindingsGateway(mgr)
130
131 // Set a keybinding.
132 require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{
133 Mode: "vim",
134 Action: "vim-scroll-left",
135 Keys: []string{"a"},
136 }))
137
138 // Reset it.
139 err := gw.ResetKeybinding(context.Background(), port.ResetKeybindingRequest{
140 Mode: "vim",
141 Action: "vim-scroll-left",
142 })
143 require.NoError(t, err)
144
145 // Reload and assert on the parsed default binding.
146 reloaded, reloadErr := NewManager()
147 require.NoError(t, reloadErr)
148 require.NoError(t, reloaded.Load())
149 assert.Equal(t, []string{"h"}, reloaded.Get().Workspace.VimMode.Actions["vim-scroll-left"].Keys)
150}
151
152func TestKeybindingsGateway_VimModeIsPartOfResetAll(t *testing.T) {
153 mgr := newTestKeybindingsManager(t)
154 gw := NewKeybindingsGateway(mgr)
155
156 // Change a vim mode binding.
157 require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{
158 Mode: "vim",
159 Action: "vim-scroll-left",
160 Keys: []string{"a"},
161 }))
162
163 // Change another action.
164 require.NoError(t, gw.SetKeybinding(context.Background(), port.SetKeybindingRequest{
165 Mode: "vim",
166 Action: "vim-scroll-down",
167 Keys: []string{"b"},
168 }))
169
170 // Reset all.
171 err := gw.ResetAllKeybindings(context.Background())
172 require.NoError(t, err)
173
174 // Reload and assert on the parsed default bindings.
175 reloaded, reloadErr := NewManager()
176 require.NoError(t, reloadErr)
177 require.NoError(t, reloaded.Load())
178 assert.Equal(t, []string{"h"}, reloaded.Get().Workspace.VimMode.Actions["vim-scroll-left"].Keys, "vim-scroll-left should be restored to default")
179 assert.Equal(t, []string{"j"}, reloaded.Get().Workspace.VimMode.Actions["vim-scroll-down"].Keys, "vim-scroll-down should be restored to default")
180}
181
182func TestKeybindingsGateway_VimModeConflictsWithinSelf(t *testing.T) {
183 gw := NewKeybindingsGateway(inMemoryManager())
184 conflicts, err := gw.CheckConflicts(context.Background(), "vim", "custom", []string{"h"})
185 require.NoError(t, err)
186 require.NotEmpty(t, conflicts, "should detect conflict with vim-scroll-left which uses 'h'")
187
188 // The conflict should reference the same mode.
189 assert.Equal(t, "vim-scroll-left", conflicts[0].ConflictingAction)
190 assert.Equal(t, "vim", conflicts[0].ConflictingMode)
191}
192
193func TestKeybindingsGateway_VimModeConflictsWithOtherModes(t *testing.T) {
194 gw := NewKeybindingsGateway(inMemoryManager())
195 // "enter" is used by vim mode's confirm - but also by other modes like pane_mode.
196 conflicts, err := gw.CheckConflicts(context.Background(), "vim", "custom", []string{"enter"})
197 require.NoError(t, err)
198
199 // There should be conflicts since "enter" is used in other modes too.
200 assert.NotEmpty(t, conflicts, "should detect cross-mode conflict for 'enter'")
201}
202
203func TestKeybindingsGateway_VimModeNoConflictForUnusedKey(t *testing.T) {
204 gw := NewKeybindingsGateway(inMemoryManager())
205 conflicts, err := gw.CheckConflicts(context.Background(), "vim", "custom", []string{"ctrl+9"})
206 require.NoError(t, err)
207 assert.Empty(t, conflicts, "no conflict expected for unused key")
208}