v0.25.0 2e6c485

manager_test.go RawHistoryFolder

1package theme
2
3import (
4	"context"
5	"testing"
6
7	"github.com/bnema/dumber/internal/application/port"
8	"github.com/bnema/dumber/internal/application/port/mocks"
9	"github.com/bnema/dumber/internal/infrastructure/config"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12)
13
14func TestNewManager_DarkMode(t *testing.T) {
15	ctx := context.Background()
16	cfg := &config.Config{
17		Appearance: config.AppearanceConfig{
18			ColorScheme: "default",
19		},
20	}
21
22	mockResolver := mocks.NewMockColorSchemeResolver(t)
23	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
24		PrefersDark: true,
25		Source:      "test",
26	})
27
28	manager := NewManager(ctx, cfg, mockResolver)
29
30	assert.True(t, manager.PrefersDark())
31	assert.Equal(t, "default", manager.scheme)
32}
33
34func TestNewManager_LightMode(t *testing.T) {
35	ctx := context.Background()
36	cfg := &config.Config{
37		Appearance: config.AppearanceConfig{
38			ColorScheme: "default",
39		},
40	}
41
42	mockResolver := mocks.NewMockColorSchemeResolver(t)
43	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
44		PrefersDark: false,
45		Source:      "test",
46	})
47
48	manager := NewManager(ctx, cfg, mockResolver)
49
50	assert.False(t, manager.PrefersDark())
51}
52
53func TestNewManager_WithNilConfig(t *testing.T) {
54	ctx := context.Background()
55
56	mockResolver := mocks.NewMockColorSchemeResolver(t)
57	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
58		PrefersDark: true,
59		Source:      "fallback",
60	})
61
62	manager := NewManager(ctx, nil, mockResolver)
63
64	require.NotNil(t, manager)
65	assert.True(t, manager.PrefersDark())
66	assert.Equal(t, "system", manager.scheme)
67}
68
69func TestNewManager_UsesConfigScheme(t *testing.T) {
70	ctx := context.Background()
71	cfg := &config.Config{
72		Appearance: config.AppearanceConfig{
73			ColorScheme: "prefer-dark",
74		},
75	}
76
77	mockResolver := mocks.NewMockColorSchemeResolver(t)
78	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
79		PrefersDark: true,
80		Source:      "config",
81	})
82
83	manager := NewManager(ctx, cfg, mockResolver)
84
85	assert.Equal(t, "prefer-dark", manager.scheme)
86}
87
88func TestManager_GetCurrentPalette_Dark(t *testing.T) {
89	ctx := context.Background()
90	cfg := &config.Config{}
91
92	mockResolver := mocks.NewMockColorSchemeResolver(t)
93	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
94		PrefersDark: true,
95		Source:      "test",
96	})
97
98	manager := NewManager(ctx, cfg, mockResolver)
99	palette := manager.GetCurrentPalette()
100
101	// Dark palette should be returned
102	assert.Equal(t, manager.GetDarkPalette(), palette)
103}
104
105func TestManager_GetCurrentPalette_Light(t *testing.T) {
106	ctx := context.Background()
107	cfg := &config.Config{}
108
109	mockResolver := mocks.NewMockColorSchemeResolver(t)
110	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
111		PrefersDark: false,
112		Source:      "test",
113	})
114
115	manager := NewManager(ctx, cfg, mockResolver)
116	palette := manager.GetCurrentPalette()
117
118	// Light palette should be returned
119	assert.Equal(t, manager.GetLightPalette(), palette)
120}
121
122func TestManager_SetColorScheme(t *testing.T) {
123	ctx := context.Background()
124	cfg := &config.Config{}
125
126	mockResolver := mocks.NewMockColorSchemeResolver(t)
127	// Initial resolve call
128	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
129		PrefersDark: true,
130		Source:      "test",
131	})
132	// Refresh call when SetColorScheme is called
133	mockResolver.EXPECT().Refresh().Return(port.ColorSchemePreference{
134		PrefersDark: false,
135		Source:      "config",
136	})
137
138	manager := NewManager(ctx, cfg, mockResolver)
139	assert.True(t, manager.PrefersDark())
140
141	// Change scheme - this calls Refresh on resolver
142	manager.SetColorScheme(ctx, "prefer-light", nil)
143
144	assert.False(t, manager.PrefersDark())
145	assert.Equal(t, "prefer-light", manager.scheme)
146}
147
148func TestManager_UpdateFromConfig(t *testing.T) {
149	ctx := context.Background()
150	cfg := &config.Config{
151		Appearance: config.AppearanceConfig{
152			ColorScheme: "default",
153		},
154	}
155
156	mockResolver := mocks.NewMockColorSchemeResolver(t)
157	// Initial resolve
158	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
159		PrefersDark: true,
160		Source:      "test",
161	})
162	// Refresh when UpdateFromConfig is called
163	mockResolver.EXPECT().Refresh().Return(port.ColorSchemePreference{
164		PrefersDark: false,
165		Source:      "config",
166	})
167
168	manager := NewManager(ctx, cfg, mockResolver)
169	assert.True(t, manager.PrefersDark())
170
171	// Update with new config
172	newCfg := &config.Config{
173		Appearance: config.AppearanceConfig{
174			ColorScheme: "prefer-light",
175		},
176	}
177	manager.UpdateFromConfig(ctx, newCfg, nil)
178
179	assert.False(t, manager.PrefersDark())
180	assert.Equal(t, "prefer-light", manager.scheme)
181}
182
183func TestManager_UpdateFromConfig_NilConfig(t *testing.T) {
184	ctx := context.Background()
185	cfg := &config.Config{}
186
187	mockResolver := mocks.NewMockColorSchemeResolver(t)
188	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
189		PrefersDark: true,
190		Source:      "test",
191	})
192
193	manager := NewManager(ctx, cfg, mockResolver)
194	initialScheme := manager.scheme
195
196	// UpdateFromConfig with nil should be a no-op
197	manager.UpdateFromConfig(ctx, nil, nil)
198
199	assert.Equal(t, initialScheme, manager.scheme)
200}
201
202func TestManager_GetWebUIThemeCSS(t *testing.T) {
203	ctx := context.Background()
204	cfg := &config.Config{}
205
206	mockResolver := mocks.NewMockColorSchemeResolver(t)
207	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
208		PrefersDark: true,
209		Source:      "test",
210	})
211
212	manager := NewManager(ctx, cfg, mockResolver)
213	css := manager.GetWebUIThemeCSS()
214
215	// Should contain both light and dark variables
216	assert.Contains(t, css, ":root{")
217	assert.Contains(t, css, ".dark{")
218}
219
220func TestManager_GetBackgroundRGBA(t *testing.T) {
221	ctx := context.Background()
222	cfg := &config.Config{}
223
224	mockResolver := mocks.NewMockColorSchemeResolver(t)
225	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
226		PrefersDark: true,
227		Source:      "test",
228	})
229
230	manager := NewManager(ctx, cfg, mockResolver)
231	r, g, b, a := manager.GetBackgroundRGBA()
232
233	// Should return valid RGBA values (0-1 range)
234	assert.GreaterOrEqual(t, r, float32(0))
235	assert.LessOrEqual(t, r, float32(1))
236	assert.GreaterOrEqual(t, g, float32(0))
237	assert.LessOrEqual(t, g, float32(1))
238	assert.GreaterOrEqual(t, b, float32(0))
239	assert.LessOrEqual(t, b, float32(1))
240	assert.GreaterOrEqual(t, a, float32(0))
241	assert.LessOrEqual(t, a, float32(1))
242}
243
244func TestManager_CustomPalettes(t *testing.T) {
245	ctx := context.Background()
246	cfg := &config.Config{
247		Appearance: config.AppearanceConfig{
248			LightPalette: config.ColorPalette{
249				Background: "#ffffff",
250				Text:       "#000000",
251			},
252			DarkPalette: config.ColorPalette{
253				Background: "#000000",
254				Text:       "#ffffff",
255			},
256		},
257	}
258
259	mockResolver := mocks.NewMockColorSchemeResolver(t)
260	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
261		PrefersDark: true,
262		Source:      "test",
263	})
264
265	manager := NewManager(ctx, cfg, mockResolver)
266
267	lightPalette := manager.GetLightPalette()
268	darkPalette := manager.GetDarkPalette()
269
270	assert.Equal(t, "#ffffff", lightPalette.Background)
271	assert.Equal(t, "#000000", lightPalette.Text)
272	assert.Equal(t, "#000000", darkPalette.Background)
273	assert.Equal(t, "#ffffff", darkPalette.Text)
274}
275
276func TestManager_UIScale(t *testing.T) {
277	ctx := context.Background()
278	cfg := &config.Config{
279		DefaultUIScale: 1.5,
280	}
281
282	mockResolver := mocks.NewMockColorSchemeResolver(t)
283	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
284		PrefersDark: true,
285		Source:      "test",
286	})
287
288	manager := NewManager(ctx, cfg, mockResolver)
289
290	// UI scale should be stored (we can't directly access it, but we can verify
291	// the manager was created without error)
292	require.NotNil(t, manager)
293}
294
295func TestManager_Fonts(t *testing.T) {
296	ctx := context.Background()
297	cfg := &config.Config{
298		Appearance: config.AppearanceConfig{
299			SansFont:      "Inter",
300			MonospaceFont: "JetBrains Mono",
301		},
302	}
303
304	mockResolver := mocks.NewMockColorSchemeResolver(t)
305	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
306		PrefersDark: true,
307		Source:      "test",
308	})
309
310	manager := NewManager(ctx, cfg, mockResolver)
311
312	// Fonts should be stored (manager creation should succeed)
313	require.NotNil(t, manager)
314}
315
316func TestManager_ModeColors(t *testing.T) {
317	ctx := context.Background()
318	cfg := &config.Config{
319		Workspace: config.WorkspaceConfig{
320			Styling: config.WorkspaceStylingConfig{
321				PaneModeColor:    "#ff0000",
322				TabModeColor:     "#00ff00",
323				SessionModeColor: "#0000ff",
324				ResizeModeColor:  "#ffff00",
325			},
326		},
327	}
328
329	mockResolver := mocks.NewMockColorSchemeResolver(t)
330	mockResolver.EXPECT().Resolve().Return(port.ColorSchemePreference{
331		PrefersDark: true,
332		Source:      "test",
333	})
334
335	manager := NewManager(ctx, cfg, mockResolver)
336	modeColors := manager.GetModeColors()
337
338	assert.Equal(t, "#ff0000", modeColors.PaneMode)
339	assert.Equal(t, "#00ff00", modeColors.TabMode)
340	assert.Equal(t, "#0000ff", modeColors.SessionMode)
341	assert.Equal(t, "#ffff00", modeColors.ResizeMode)
342}

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