v0.27.0 dcb4c51

modal_sizing_test.go RawHistoryFolder

1package component
2
3import (
4	"testing"
5
6	layoutmocks "github.com/bnema/dumber/internal/ui/layout/mocks"
7	"github.com/stretchr/testify/assert"
8)
9
10func TestCalculateModalDimensions_NilParent(t *testing.T) {
11	cfg := ModalSizeConfig{
12		WidthPct:       0.6,
13		MaxWidth:       600,
14		TopMarginPct:   0.15,
15		FallbackWidth:  800,
16		FallbackHeight: 600,
17	}
18
19	width, marginTop := CalculateModalDimensions(nil, cfg)
20
21	// Should use fallback values
22	expectedWidth := int(float64(cfg.FallbackWidth) * cfg.WidthPct)
23	expectedMargin := int(float64(cfg.FallbackHeight) * cfg.TopMarginPct)
24
25	assert.Equal(t, expectedWidth, width)
26	assert.Equal(t, expectedMargin, marginTop)
27}
28
29func TestCalculateModalDimensions_ZeroAllocatedSize(t *testing.T) {
30	cfg := ModalSizeConfig{
31		WidthPct:       0.5,
32		MaxWidth:       500,
33		TopMarginPct:   0.2,
34		FallbackWidth:  1000,
35		FallbackHeight: 800,
36	}
37
38	parent := layoutmocks.NewMockOverlayWidget(t)
39	parent.EXPECT().GetAllocatedWidth().Return(0)
40	parent.EXPECT().GetAllocatedHeight().Return(0)
41
42	width, marginTop := CalculateModalDimensions(parent, cfg)
43
44	// Should use fallback values when allocated size is 0
45	expectedWidth := int(float64(cfg.FallbackWidth) * cfg.WidthPct)
46	expectedMargin := int(float64(cfg.FallbackHeight) * cfg.TopMarginPct)
47
48	assert.Equal(t, expectedWidth, width)
49	assert.Equal(t, expectedMargin, marginTop)
50}
51
52func TestCalculateModalDimensions_WidthCapped(t *testing.T) {
53	cfg := ModalSizeConfig{
54		WidthPct:       0.8,
55		MaxWidth:       600,
56		TopMarginPct:   0.1,
57		FallbackWidth:  800,
58		FallbackHeight: 600,
59	}
60
61	parent := layoutmocks.NewMockOverlayWidget(t)
62	parent.EXPECT().GetAllocatedWidth().Return(1920) // Large screen
63	parent.EXPECT().GetAllocatedHeight().Return(1080)
64
65	width, marginTop := CalculateModalDimensions(parent, cfg)
66
67	// 0.8 * 1920 = 1536, but capped at MaxWidth 600
68	assert.Equal(t, cfg.MaxWidth, width)
69	assert.Equal(t, int(float64(1080)*cfg.TopMarginPct), marginTop)
70}
71
72func TestCalculateModalDimensions_WidthPercentage(t *testing.T) {
73	cfg := ModalSizeConfig{
74		WidthPct:       0.6,
75		MaxWidth:       1000, // High cap, won't be hit
76		TopMarginPct:   0.15,
77		FallbackWidth:  800,
78		FallbackHeight: 600,
79	}
80
81	parent := layoutmocks.NewMockOverlayWidget(t)
82	parent.EXPECT().GetAllocatedWidth().Return(800)
83	parent.EXPECT().GetAllocatedHeight().Return(600)
84
85	width, marginTop := CalculateModalDimensions(parent, cfg)
86
87	assert.Equal(t, 480, width)
88	assert.Equal(t, 90, marginTop)
89}
90
91func TestScaleValue_DefaultScale(t *testing.T) {
92	// When scale is 0 or negative, should default to 1.0
93	assert.Equal(t, 50, ScaleValue(50, 0))
94	assert.Equal(t, 50, ScaleValue(50, -1.0))
95}
96
97func TestScaleValue_NormalScale(t *testing.T) {
98	assert.Equal(t, 50, ScaleValue(50, 1.0))
99}
100
101func TestScaleValue_Scaling(t *testing.T) {
102	// 1.5x scale
103	assert.Equal(t, 75, ScaleValue(50, 1.5))
104	// 2x scale
105	assert.Equal(t, 100, ScaleValue(50, 2.0))
106	// 1.2x scale (common UI scale)
107	assert.Equal(t, 60, ScaleValue(50, 1.2))
108}
109
110func TestMeasureWidgetHeight_NilWidget(t *testing.T) {
111	height := MeasureWidgetHeight(nil, 100)
112	assert.Equal(t, 0, height)
113}
114
115func TestDefaultRowHeights_Values(t *testing.T) {
116	// Ensure defaults are sensible
117	assert.Positive(t, DefaultRowHeights.Standard)
118	assert.Positive(t, DefaultRowHeights.Compact)
119	assert.Positive(t, DefaultRowHeights.Divider)
120	// Standard should be larger than compact
121	assert.Greater(t, DefaultRowHeights.Standard, DefaultRowHeights.Compact)
122}
123
124func TestOmniboxSizeDefaults_Values(t *testing.T) {
125	assert.Greater(t, OmniboxSizeDefaults.WidthPct, 0.0)
126	assert.LessOrEqual(t, OmniboxSizeDefaults.WidthPct, 1.0)
127	assert.Positive(t, OmniboxSizeDefaults.MaxWidth)
128	assert.Greater(t, OmniboxSizeDefaults.TopMarginPct, 0.0)
129	assert.LessOrEqual(t, OmniboxSizeDefaults.TopMarginPct, 1.0)
130}
131
132func TestSessionManagerSizeDefaults_Values(t *testing.T) {
133	assert.Greater(t, SessionManagerSizeDefaults.WidthPct, 0.0)
134	assert.LessOrEqual(t, SessionManagerSizeDefaults.WidthPct, 1.0)
135	assert.Positive(t, SessionManagerSizeDefaults.MaxWidth)
136	assert.Greater(t, SessionManagerSizeDefaults.TopMarginPct, 0.0)
137	assert.LessOrEqual(t, SessionManagerSizeDefaults.TopMarginPct, 1.0)
138}
139
140func TestPermissionPopupSizeDefaults_Values(t *testing.T) {
141	assert.Greater(t, PermissionPopupSizeDefaults.WidthPct, 0.0)
142	assert.LessOrEqual(t, PermissionPopupSizeDefaults.WidthPct, 1.0)
143	assert.Positive(t, PermissionPopupSizeDefaults.MaxWidth)
144	assert.Greater(t, PermissionPopupSizeDefaults.TopMarginPct, 0.0)
145	assert.LessOrEqual(t, PermissionPopupSizeDefaults.TopMarginPct, 1.0)
146	assert.Equal(t, 300, PermissionPopupSizeDefaults.FallbackHeight)
147}
148
149func TestListDisplayDefaults_Values(t *testing.T) {
150	assert.Positive(t, OmniboxListDefaults.MaxVisibleRows)
151	assert.Positive(t, OmniboxListDefaults.MaxResults)
152	assert.Positive(t, SessionManagerListDefaults.MaxVisibleRows)
153	assert.Positive(t, SessionManagerListDefaults.MaxResults)
154}
155
156func TestEffectiveMaxRows(t *testing.T) {
157	sizeCfg := ModalSizeConfig{TopMarginPct: 0.2}
158	defaults := ListDisplayDefaults{
159		MaxVisibleRows:      10,
160		MaxResults:          10,
161		SmallMaxVisibleRows: 5,
162	}
163
164	tests := []struct {
165		name         string
166		parentHeight int
167		rowHeight    int
168		want         int
169	}{
170		{
171			name:         "tall pane returns MaxVisibleRows",
172			parentHeight: 1200,
173			rowHeight:    72,
174			want:         10,
175		},
176		{
177			name:         "short pane returns SmallMaxVisibleRows",
178			parentHeight: 800,
179			rowHeight:    72,
180			want:         5,
181		},
182		{
183			name:         "zero parentHeight returns MaxVisibleRows",
184			parentHeight: 0,
185			rowHeight:    72,
186			want:         10,
187		},
188		{
189			name:         "zero rowHeight returns MaxVisibleRows",
190			parentHeight: 800,
191			rowHeight:    0,
192			want:         10,
193		},
194		{
195			name:         "negative parentHeight returns MaxVisibleRows",
196			parentHeight: -100,
197			rowHeight:    72,
198			want:         10,
199		},
200		{
201			name:         "exact fit returns MaxVisibleRows",
202			parentHeight: 1150, // available = 1150 - 230 - 144 = 776, needed = 720
203			rowHeight:    72,
204			want:         10,
205		},
206		{
207			name:         "boundary: available equals needed still fits",
208			parentHeight: 1080, // available = 1080 - 216 - 144 = 720, needed = 720 → fits
209			rowHeight:    72,
210			want:         10,
211		},
212		{
213			name:         "boundary: one pixel under triggers small",
214			parentHeight: 1079, // available = 1079 - 215 - 144 = 720, needed = 720 → check
215			rowHeight:    72,
216			want:         10, // int(1079*0.2) = 215, 1079-215-144 = 720 → still fits
217		},
218		{
219			name:         "just below boundary triggers small",
220			parentHeight: 1078, // available = 1078 - 215 - 144 = 719, needed = 720 → doesn't fit
221			rowHeight:    72,
222			want:         5,
223		},
224	}
225
226	for _, tt := range tests {
227		t.Run(tt.name, func(t *testing.T) {
228			got := EffectiveMaxRows(tt.parentHeight, tt.rowHeight, sizeCfg, defaults)
229			assert.Equal(t, tt.want, got)
230		})
231	}
232}
233
234func TestEffectiveMaxRows_NoAdaptation(t *testing.T) {
235	sizeCfg := ModalSizeConfig{TopMarginPct: 0.2}
236	defaults := ListDisplayDefaults{
237		MaxVisibleRows:      10,
238		MaxResults:          10,
239		SmallMaxVisibleRows: 0, // adaptation disabled
240	}
241
242	// Even with a short pane, should return MaxVisibleRows when SmallMaxVisibleRows is 0
243	got := EffectiveMaxRows(500, 72, sizeCfg, defaults)
244	assert.Equal(t, 10, got)
245}

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