1package component
2
3import (
4 "math"
5 "testing"
6
7 layoutmocks "github.com/bnema/dumber/internal/ui/layout/mocks"
8 "github.com/bnema/puregotk/v4/gtk"
9 "github.com/stretchr/testify/assert"
10)
11
12func TestCalculateModalDimensions_NilParent(t *testing.T) {
13 cfg := ModalSizeConfig{
14 WidthPct: 0.6,
15 MaxWidth: 600,
16 TopMarginPct: 0.15,
17 FallbackWidth: 800,
18 FallbackHeight: 600,
19 }
20
21 width, marginTop := CalculateModalDimensions(nil, cfg)
22
23 // Should use fallback values
24 expectedWidth := int(float64(cfg.FallbackWidth) * cfg.WidthPct)
25 expectedMargin := int(float64(cfg.FallbackHeight) * cfg.TopMarginPct)
26
27 assert.Equal(t, expectedWidth, width)
28 assert.Equal(t, expectedMargin, marginTop)
29}
30
31func TestCalculateModalDimensions_ZeroAllocatedSize(t *testing.T) {
32 cfg := ModalSizeConfig{
33 WidthPct: 0.5,
34 MaxWidth: 500,
35 TopMarginPct: 0.2,
36 FallbackWidth: 1000,
37 FallbackHeight: 800,
38 }
39
40 parent := layoutmocks.NewMockOverlayWidget(t)
41 parent.EXPECT().GetAllocatedWidth().Return(0)
42 parent.EXPECT().GetAllocatedHeight().Return(0)
43
44 width, marginTop := CalculateModalDimensions(parent, cfg)
45
46 // Should use fallback values when allocated size is 0
47 expectedWidth := int(float64(cfg.FallbackWidth) * cfg.WidthPct)
48 expectedMargin := int(float64(cfg.FallbackHeight) * cfg.TopMarginPct)
49
50 assert.Equal(t, expectedWidth, width)
51 assert.Equal(t, expectedMargin, marginTop)
52}
53
54func TestCalculateModalDimensions_WidthCapped(t *testing.T) {
55 cfg := ModalSizeConfig{
56 WidthPct: 0.8,
57 MaxWidth: 600,
58 TopMarginPct: 0.1,
59 FallbackWidth: 800,
60 FallbackHeight: 600,
61 }
62
63 parent := layoutmocks.NewMockOverlayWidget(t)
64 parent.EXPECT().GetAllocatedWidth().Return(1920) // Large screen
65 parent.EXPECT().GetAllocatedHeight().Return(1080)
66
67 width, marginTop := CalculateModalDimensions(parent, cfg)
68
69 // 0.8 * 1920 = 1536, but capped at MaxWidth 600
70 assert.Equal(t, cfg.MaxWidth, width)
71 assert.Equal(t, int(float64(1080)*cfg.TopMarginPct), marginTop)
72}
73
74func TestCalculateModalDimensions_WidthPercentage(t *testing.T) {
75 cfg := ModalSizeConfig{
76 WidthPct: 0.6,
77 MaxWidth: 1000, // High cap, won't be hit
78 TopMarginPct: 0.15,
79 FallbackWidth: 800,
80 FallbackHeight: 600,
81 }
82
83 parent := layoutmocks.NewMockOverlayWidget(t)
84 parent.EXPECT().GetAllocatedWidth().Return(800)
85 parent.EXPECT().GetAllocatedHeight().Return(600)
86
87 width, marginTop := CalculateModalDimensions(parent, cfg)
88
89 assert.Equal(t, 480, width)
90 assert.Equal(t, 90, marginTop)
91}
92
93func TestCalculateModalDimensions_FixedWidthAndTopMargin(t *testing.T) {
94 cfg := ModalSizeConfig{
95 WidthPct: 0.8,
96 MaxWidth: 600,
97 TopMarginPct: 0.2,
98 FallbackWidth: 1000,
99 FallbackHeight: 800,
100 FixedWidth: 800,
101 FixedTopMargin: 0,
102 UseFixedTopMargin: true,
103 }
104
105 parent := layoutmocks.NewMockOverlayWidget(t)
106 parent.EXPECT().GetAllocatedWidth().Return(1920)
107 parent.EXPECT().GetAllocatedHeight().Return(1080)
108
109 width, marginTop := CalculateModalDimensions(parent, cfg)
110
111 assert.Equal(t, 800, width)
112 assert.Equal(t, 0, marginTop)
113}
114
115func TestResolveModalSizeConfig_MergesDefaultsAndOverrides(t *testing.T) {
116 defaults := OmniboxSizeDefaults
117 override := ModalSizeConfig{
118 FixedWidth: 800,
119 FixedTopMargin: 0,
120 UseFixedTopMargin: true,
121 UseMonitorHeight: true,
122 }
123
124 resolved := ResolveModalSizeConfig(override, defaults)
125
126 assert.InDelta(t, defaults.WidthPct, resolved.WidthPct, 0)
127 assert.Equal(t, defaults.MaxWidth, resolved.MaxWidth)
128 assert.Equal(t, defaults.FallbackWidth, resolved.FallbackWidth)
129 assert.Equal(t, defaults.FallbackHeight, resolved.FallbackHeight)
130 assert.Equal(t, 800, resolved.FixedWidth)
131 assert.True(t, resolved.UseFixedTopMargin)
132 assert.Equal(t, 0, resolved.FixedTopMargin)
133 assert.True(t, resolved.UseMonitorHeight)
134}
135
136func TestScaleValue_DefaultScale(t *testing.T) {
137 // When scale is 0 or negative, should default to 1.0
138 assert.Equal(t, 50, ScaleValue(50, 0))
139 assert.Equal(t, 50, ScaleValue(50, -1.0))
140}
141
142func TestScaleValue_NormalScale(t *testing.T) {
143 assert.Equal(t, 50, ScaleValue(50, 1.0))
144}
145
146func TestScaleValue_Scaling(t *testing.T) {
147 // 1.5x scale
148 assert.Equal(t, 75, ScaleValue(50, 1.5))
149 // 2x scale
150 assert.Equal(t, 100, ScaleValue(50, 2.0))
151 // 1.2x scale (common UI scale)
152 assert.Equal(t, 60, ScaleValue(50, 1.2))
153}
154
155func TestMeasureWidgetHeight_NilWidget(t *testing.T) {
156 height := MeasureWidgetHeight(nil, 100)
157 assert.Equal(t, 0, height)
158}
159
160func TestDefaultRowHeights_Values(t *testing.T) {
161 // Ensure defaults are sensible
162 assert.Positive(t, DefaultRowHeights.Standard)
163 assert.Positive(t, DefaultRowHeights.Compact)
164 assert.Positive(t, DefaultRowHeights.Divider)
165 // Standard should be larger than compact
166 assert.Greater(t, DefaultRowHeights.Standard, DefaultRowHeights.Compact)
167}
168
169func TestOmniboxSizeDefaults_Values(t *testing.T) {
170 assert.InDelta(t, 0.8, OmniboxSizeDefaults.WidthPct, 0.0001)
171 assert.Equal(t, 800, OmniboxSizeDefaults.MaxWidth)
172 assert.Zero(t, OmniboxSizeDefaults.TopMarginPct)
173 assert.Equal(t, 600, OmniboxSizeDefaults.FallbackHeight)
174}
175
176func TestOmniboxValign(t *testing.T) {
177 assert.Equal(t, gtk.AlignCenterValue, omniboxValign(OmniboxSizeDefaults))
178 assert.Equal(t, gtk.AlignStartValue, omniboxValign(ModalSizeConfig{UseFixedTopMargin: true}))
179 assert.Equal(t, gtk.AlignStartValue, omniboxValign(ResolveModalSizeConfig(
180 ModalSizeConfig{TopMarginPct: 0.2}, OmniboxSizeDefaults)))
181}
182
183func TestOmniboxListDefaults_ScrollableResults(t *testing.T) {
184 assert.Less(t, OmniboxListDefaults.MaxVisibleRows, OmniboxListDefaults.MaxResults)
185 assert.Less(t, OmniboxListDefaults.SmallMaxVisibleRows, OmniboxListDefaults.MaxVisibleRows)
186}
187
188func TestSessionManagerSizeDefaults_Values(t *testing.T) {
189 assert.Greater(t, SessionManagerSizeDefaults.WidthPct, 0.0)
190 assert.LessOrEqual(t, SessionManagerSizeDefaults.WidthPct, 1.0)
191 assert.Positive(t, SessionManagerSizeDefaults.MaxWidth)
192 assert.Greater(t, SessionManagerSizeDefaults.TopMarginPct, 0.0)
193 assert.LessOrEqual(t, SessionManagerSizeDefaults.TopMarginPct, 1.0)
194}
195
196func TestPermissionPopupSizeDefaults_Values(t *testing.T) {
197 assert.Greater(t, PermissionPopupSizeDefaults.WidthPct, 0.0)
198 assert.LessOrEqual(t, PermissionPopupSizeDefaults.WidthPct, 1.0)
199 assert.Positive(t, PermissionPopupSizeDefaults.MaxWidth)
200 assert.Greater(t, PermissionPopupSizeDefaults.TopMarginPct, 0.0)
201 assert.LessOrEqual(t, PermissionPopupSizeDefaults.TopMarginPct, 1.0)
202 assert.Equal(t, 300, PermissionPopupSizeDefaults.FallbackHeight)
203}
204
205func TestListDisplayDefaults_Values(t *testing.T) {
206 assert.Positive(t, OmniboxListDefaults.MaxVisibleRows)
207 assert.Positive(t, OmniboxListDefaults.MaxResults)
208 assert.Positive(t, SessionManagerListDefaults.MaxVisibleRows)
209 assert.Positive(t, SessionManagerListDefaults.MaxResults)
210}
211
212func TestEffectiveMaxRows(t *testing.T) {
213 sizeCfg := ModalSizeConfig{TopMarginPct: 0.2}
214 defaults := ListDisplayDefaults{
215 MaxVisibleRows: 10,
216 MaxResults: 10,
217 SmallMaxVisibleRows: 5,
218 }
219
220 tests := []struct {
221 name string
222 parentHeight int
223 rowHeight int
224 want int
225 }{
226 {
227 name: "tall pane returns MaxVisibleRows",
228 parentHeight: 1200,
229 rowHeight: 72,
230 want: 10,
231 },
232 {
233 name: "short pane returns SmallMaxVisibleRows",
234 parentHeight: 800,
235 rowHeight: 72,
236 want: 5,
237 },
238 {
239 name: "zero parentHeight returns MaxVisibleRows",
240 parentHeight: 0,
241 rowHeight: 72,
242 want: 10,
243 },
244 {
245 name: "zero rowHeight returns MaxVisibleRows",
246 parentHeight: 800,
247 rowHeight: 0,
248 want: 10,
249 },
250 {
251 name: "negative parentHeight returns MaxVisibleRows",
252 parentHeight: -100,
253 rowHeight: 72,
254 want: 10,
255 },
256 {
257 name: "exact fit returns MaxVisibleRows",
258 parentHeight: 1150, // available = 1150 - 230 - 144 = 776, needed = 720
259 rowHeight: 72,
260 want: 10,
261 },
262 {
263 name: "boundary: available equals needed still fits",
264 parentHeight: 1080, // available = 1080 - 216 - 144 = 720, needed = 720 → fits
265 rowHeight: 72,
266 want: 10,
267 },
268 {
269 name: "boundary: one pixel under triggers small",
270 parentHeight: 1079, // available = 1079 - 215 - 144 = 720, needed = 720 → check
271 rowHeight: 72,
272 want: 10, // int(1079*0.2) = 215, 1079-215-144 = 720 → still fits
273 },
274 {
275 name: "just below boundary triggers small",
276 parentHeight: 1078, // available = 1078 - 215 - 144 = 719, needed = 720 → doesn't fit
277 rowHeight: 72,
278 want: 5,
279 },
280 }
281
282 for _, tt := range tests {
283 t.Run(tt.name, func(t *testing.T) {
284 got := EffectiveMaxRows(tt.parentHeight, tt.rowHeight, sizeCfg, defaults)
285 assert.Equal(t, tt.want, got)
286 })
287 }
288}
289
290func TestEffectiveMaxRows_NoAdaptation(t *testing.T) {
291 sizeCfg := ModalSizeConfig{TopMarginPct: 0.2}
292 defaults := ListDisplayDefaults{
293 MaxVisibleRows: 10,
294 MaxResults: 10,
295 SmallMaxVisibleRows: 0, // adaptation disabled
296 }
297
298 // Even with a short pane, should return MaxVisibleRows when SmallMaxVisibleRows is 0
299 got := EffectiveMaxRows(500, 72, sizeCfg, defaults)
300 assert.Equal(t, 10, got)
301}
302
303func mockParent(t *testing.T, w, h int) *layoutmocks.MockOverlayWidget {
304 t.Helper()
305 parent := layoutmocks.NewMockOverlayWidget(t)
306 parent.EXPECT().GetAllocatedWidth().Return(w).Maybe()
307 parent.EXPECT().GetAllocatedHeight().Return(h).Maybe()
308 return parent
309}
310
311func TestCalculateModalDimensionsWithScale_ScaleOneUnchanged(t *testing.T) {
312 cfg := ModalSizeConfig{
313 WidthPct: 0.8,
314 MaxWidth: 800,
315 TopMarginPct: 0.2,
316 FallbackWidth: 800,
317 FallbackHeight: 600,
318 }
319 parent := mockParent(t, 1920, 1080)
320 w, m := CalculateModalDimensionsWithScale(parent, cfg, 1)
321 wLegacy, mLegacy := CalculateModalDimensions(parent, cfg)
322 assert.Equal(t, wLegacy, w)
323 assert.Equal(t, mLegacy, m)
324 assert.Equal(t, 800, w)
325 assert.Equal(t, 216, m)
326}
327
328func TestCalculateModalDimensionsWithScale_LargeParent(t *testing.T) {
329 cfg := OmniboxSizeDefaults
330 for _, tt := range []struct {
331 scale float64
332 want int
333 }{
334 {1.25, 1000},
335 {1.5, 1200},
336 {2.0, 1600},
337 } {
338 parent := mockParent(t, 3000, 1200)
339 w, m := CalculateModalDimensionsWithScale(parent, cfg, tt.scale)
340 assert.Equal(t, tt.want, w, "scale %v", tt.scale)
341 assert.Zero(t, m, "omnibox is centered, not top-margined")
342 }
343}
344
345func TestCalculateModalDimensionsWithScale_SmallParentPercentageBound(t *testing.T) {
346 cfg := SessionManagerSizeDefaults // 0.6 * w, max 600
347 parent := mockParent(t, 500, 800)
348 w, m := CalculateModalDimensionsWithScale(parent, cfg, 2.0)
349 // 0.6*500=300 < 600*2 → percentage bound wins
350 assert.Equal(t, 300, w)
351 assert.Equal(t, int(float64(800)*cfg.TopMarginPct), m)
352}
353
354func TestCalculateModalDimensionsWithScale_ScaleBelowOne(t *testing.T) {
355 cfg := OmniboxSizeDefaults
356 parent := mockParent(t, 3000, 1080)
357 w, _ := CalculateModalDimensionsWithScale(parent, cfg, 0.5)
358 assert.Equal(t, 400, w)
359}
360
361func TestCalculateModalDimensionsWithScale_NormalizesBadScale(t *testing.T) {
362 cfg := OmniboxSizeDefaults
363 for _, scale := range []float64{0, -1, -0.5, nan(), inf(1), inf(-1)} {
364 parent := mockParent(t, 3000, 1080)
365 w, m := CalculateModalDimensionsWithScale(parent, cfg, scale)
366 assert.Equal(t, 800, w, "scale %v", scale)
367 assert.Zero(t, m, "scale %v", scale)
368 }
369}
370
371func TestCalculateModalDimensionsWithScale_FallbackScaled(t *testing.T) {
372 cfg := OmniboxSizeDefaults
373 parent := mockParent(t, 0, 0)
374 w, m := CalculateModalDimensionsWithScale(parent, cfg, 1.5)
375 // fallback 800*1.5=1200 → pct 0.8*1200=960 vs max 1200 → 960
376 assert.Equal(t, 960, w)
377 assert.Equal(t, int(float64(600)*cfg.TopMarginPct), m)
378}
379
380func TestCalculateModalDimensionsWithScale_FixedScalesOnceBeyondAllocation(t *testing.T) {
381 cfg := ResolveModalSizeConfig(ModalSizeConfig{
382 FixedWidth: 800,
383 FixedTopMargin: 0,
384 UseFixedTopMargin: true,
385 }, OmniboxSizeDefaults)
386 // Child-sized host allocation (small) must not clamp the fixed request.
387 parent := mockParent(t, 120, 200)
388 w, m := CalculateModalDimensionsWithScale(parent, cfg, 1.5)
389 assert.Equal(t, 1200, w)
390 assert.Equal(t, 0, m)
391 // Config must not be mutated by repeated calls.
392 assert.Equal(t, 800, cfg.FixedWidth)
393 w2, _ := CalculateModalDimensionsWithScale(parent, cfg, 1.5)
394 assert.Equal(t, w, w2)
395 assert.Equal(t, 800, cfg.FixedWidth)
396 assert.Equal(t, 800, OmniboxSizeDefaults.MaxWidth)
397}
398
399func TestCalculateModalDimensionsWithScale_NoDefaultMutation(t *testing.T) {
400 before := OmniboxSizeDefaults
401 parent := mockParent(t, 3000, 1200)
402 _, _ = CalculateModalDimensionsWithScale(parent, OmniboxSizeDefaults, 2.0)
403 assert.Equal(t, before, OmniboxSizeDefaults)
404}
405
406func TestOmniboxRequestedDimensions_UsesScale(t *testing.T) {
407 cfg := ResolveModalSizeConfig(ModalSizeConfig{
408 FixedWidth: 800,
409 FixedTopMargin: 0,
410 UseFixedTopMargin: true,
411 }, OmniboxSizeDefaults)
412 o := &Omnibox{parentOverlay: mockParent(t, 120, 200), sizeCfg: cfg, uiScale: 1.5}
413 w, m := o.requestedDimensions()
414 assert.Equal(t, 1200, w)
415 assert.Equal(t, 0, m)
416}
417
418func TestSessionManagerRequestedDimensions_UsesScale(t *testing.T) {
419 sm := &SessionManager{parentOverlay: mockParent(t, 3000, 1200), uiScale: 1.5}
420 w, m := sm.requestedDimensions()
421 // 0.6*3000=1800 vs 600*1.5=900 → 900
422 assert.Equal(t, 900, w)
423 assert.Equal(t, int(float64(1200)*SessionManagerSizeDefaults.TopMarginPct), m)
424}
425
426func nan() float64 { return math.NaN() }
427
428func inf(sign int) float64 { return math.Inf(sign) }