v0.30.0 eb3068f

detector_test.go RawHistoryFolder

1package fonts
2
3import (
4	"context"
5	"testing"
6
7	"github.com/bnema/dumber/internal/application/port"
8	"github.com/rs/zerolog"
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11)
12
13func testContext() context.Context {
14	logger := zerolog.Nop()
15	return logger.WithContext(context.Background())
16}
17
18func TestDetector_IsAvailable(t *testing.T) {
19	ctx := testContext()
20	detector := NewDetector()
21
22	// fc-list should be available on most Linux systems
23	// This test documents the behavior rather than asserting a specific value
24	available := detector.IsAvailable(ctx)
25	t.Logf("fc-list available: %v", available)
26}
27
28func TestDetector_GetAvailableFonts(t *testing.T) {
29	ctx := testContext()
30	detector := NewDetector()
31
32	if !detector.IsAvailable(ctx) {
33		t.Skip("fc-list not available on this system")
34	}
35
36	fonts, err := detector.GetAvailableFonts(ctx)
37	require.NoError(t, err)
38	assert.NotEmpty(t, fonts, "expected at least some fonts to be installed")
39
40	t.Logf("Found %d font families", len(fonts))
41}
42
43func TestDetector_GetAvailableFonts_Caching(t *testing.T) {
44	ctx := testContext()
45	detector := NewDetector()
46
47	if !detector.IsAvailable(ctx) {
48		t.Skip("fc-list not available on this system")
49	}
50
51	// First call populates cache
52	fonts1, err := detector.GetAvailableFonts(ctx)
53	require.NoError(t, err)
54
55	// Second call should return cached result
56	fonts2, err := detector.GetAvailableFonts(ctx)
57	require.NoError(t, err)
58
59	assert.Len(t, fonts2, len(fonts1), "cached result should match original")
60}
61
62func TestDetector_SelectBestFont_SansSerif(t *testing.T) {
63	ctx := testContext()
64	detector := NewDetector()
65
66	if !detector.IsAvailable(ctx) {
67		t.Skip("fc-list not available on this system")
68	}
69
70	font := detector.SelectBestFont(ctx, port.FontCategorySansSerif, SansSerifFallbackChain())
71	assert.NotEmpty(t, font)
72
73	// Should either be a font from the chain or the generic fallback
74	isFromChain := false
75	for _, f := range SansSerifFallbackChain() {
76		if font == f {
77			isFromChain = true
78			break
79		}
80	}
81	if !isFromChain {
82		assert.Equal(t, "sans-serif", font, "should fall back to generic if no chain fonts available")
83	}
84
85	t.Logf("Selected sans-serif font: %s", font)
86}
87
88func TestDetector_SelectBestFont_Serif(t *testing.T) {
89	ctx := testContext()
90	detector := NewDetector()
91
92	if !detector.IsAvailable(ctx) {
93		t.Skip("fc-list not available on this system")
94	}
95
96	font := detector.SelectBestFont(ctx, port.FontCategorySerif, SerifFallbackChain())
97	assert.NotEmpty(t, font)
98
99	isFromChain := false
100	for _, f := range SerifFallbackChain() {
101		if font == f {
102			isFromChain = true
103			break
104		}
105	}
106	if !isFromChain {
107		assert.Equal(t, "serif", font, "should fall back to generic if no chain fonts available")
108	}
109
110	t.Logf("Selected serif font: %s", font)
111}
112
113func TestDetector_SelectBestFont_Monospace(t *testing.T) {
114	ctx := testContext()
115	detector := NewDetector()
116
117	if !detector.IsAvailable(ctx) {
118		t.Skip("fc-list not available on this system")
119	}
120
121	font := detector.SelectBestFont(ctx, port.FontCategoryMonospace, MonospaceFallbackChain())
122	assert.NotEmpty(t, font)
123
124	isFromChain := false
125	for _, f := range MonospaceFallbackChain() {
126		if font == f {
127			isFromChain = true
128			break
129		}
130	}
131	if !isFromChain {
132		assert.Equal(t, "monospace", font, "should fall back to generic if no chain fonts available")
133	}
134
135	t.Logf("Selected monospace font: %s", font)
136}
137
138func TestDetector_SelectBestFont_EmptyChain(t *testing.T) {
139	ctx := testContext()
140	detector := NewDetector()
141
142	if !detector.IsAvailable(ctx) {
143		t.Skip("fc-list not available on this system")
144	}
145
146	// Empty chain should return generic fallback
147	font := detector.SelectBestFont(ctx, port.FontCategorySansSerif, []string{})
148	assert.Equal(t, "sans-serif", font)
149
150	font = detector.SelectBestFont(ctx, port.FontCategorySerif, []string{})
151	assert.Equal(t, "serif", font)
152
153	font = detector.SelectBestFont(ctx, port.FontCategoryMonospace, []string{})
154	assert.Equal(t, "monospace", font)
155}
156
157func TestDetector_SelectBestFont_NoMatchingFonts(t *testing.T) {
158	ctx := testContext()
159	detector := NewDetector()
160
161	if !detector.IsAvailable(ctx) {
162		t.Skip("fc-list not available on this system")
163	}
164
165	// Chain with fonts that definitely don't exist
166	chain := []string{
167		"NonExistentFont1234",
168		"AnotherFakeFont5678",
169	}
170
171	font := detector.SelectBestFont(ctx, port.FontCategorySansSerif, chain)
172	assert.Equal(t, "sans-serif", font, "should fall back to generic when no fonts match")
173}
174
175func TestDetector_genericFallback(t *testing.T) {
176	detector := NewDetector()
177
178	tests := []struct {
179		category port.FontCategory
180		expected string
181	}{
182		{port.FontCategorySansSerif, "sans-serif"},
183		{port.FontCategorySerif, "serif"},
184		{port.FontCategoryMonospace, "monospace"},
185		{port.FontCategory("unknown"), "sans-serif"}, // default case
186	}
187
188	for _, tt := range tests {
189		t.Run(string(tt.category), func(t *testing.T) {
190			result := detector.genericFallback(tt.category)
191			assert.Equal(t, tt.expected, result)
192		})
193	}
194}
195
196func TestFallbackChains_NotEmpty(t *testing.T) {
197	assert.NotEmpty(t, SansSerifFallbackChain(), "sans-serif fallback chain should not be empty")
198	assert.NotEmpty(t, SerifFallbackChain(), "serif fallback chain should not be empty")
199	assert.NotEmpty(t, MonospaceFallbackChain(), "monospace fallback chain should not be empty")
200}
201
202func TestFallbackChains_PreferredFontsFirst(t *testing.T) {
203	// Verify preferred fonts are first in chain
204	assert.Equal(t, "Fira Sans", SansSerifFallbackChain()[0], "Fira Sans should be first in sans-serif chain")
205	assert.Equal(t, "Noto Serif", SerifFallbackChain()[0], "Noto Serif should be first in serif chain")
206	assert.Equal(t, "Fira Code", MonospaceFallbackChain()[0], "Fira Code should be first in monospace chain")
207}

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