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