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