main 5041353

theme_test.go RawHistoryFolder

1package systemviews
2
3import (
4	"testing"
5
6	"github.com/bnema/dumber/internal/application/dto"
7	"github.com/stretchr/testify/assert"
8)
9
10func TestBuildInlineVarsRejectsUnsafeCSSValues(t *testing.T) {
11	t.Parallel()
12
13	vars := buildInlineVars(dto.WebUIAppearanceConfig{
14		SansFont:      "Inter; color:red",
15		SerifFont:     "Georgia",
16		MonospaceFont: "JetBrains Mono",
17		LightPalette: dto.ColorPalette{
18			Background: "#ffffff; color:red",
19			Surface:    "rgb(1 2 3 / 50%)",
20			Text:       "transparent",
21			Accent:     "definitelynotacolor",
22			Border:     "#ABCDEF80",
23		},
24	}, dto.ColorPalette{
25		Background: "#ffffff; color:red",
26		Surface:    "rgb(1 2 3 / 50%)",
27		Text:       "transparent",
28		Accent:     "definitelynotacolor",
29		Border:     "#ABCDEF80",
30	})
31
32	assert.NotContains(t, vars, "color:red")
33	assert.NotContains(t, vars, "--sv-background")
34	assert.NotContains(t, vars, "--sv-font-sans")
35	assert.Contains(t, vars, "--sv-surface: rgb(1 2 3 / 50%);")
36	assert.Contains(t, vars, "--sv-text: transparent;")
37	assert.NotContains(t, vars, "definitelynotacolor")
38	assert.Contains(t, vars, "--sv-border: #ABCDEF80;")
39	assert.Contains(t, vars, "--sv-font-serif: Georgia;")
40	assert.Contains(t, vars, "--sv-font-mono: JetBrains Mono;")
41}
42
43func TestSanitizeCSSFontFamilyQuotes(t *testing.T) {
44	t.Parallel()
45
46	tests := []struct {
47		name  string
48		value string
49		want  bool
50	}{
51		{name: "unquoted spaces", value: "JetBrains Mono", want: true},
52		{name: "single quoted token", value: "'JetBrains Mono'", want: true},
53		{name: "double quoted token", value: "\"JetBrains Mono\"", want: true},
54		{name: "unquoted single quote", value: "JetBrains' Mono", want: false},
55		{name: "unquoted double quote", value: "JetBrains\" Mono", want: false},
56		{name: "internal quote", value: "'JetBrains' Mono'", want: false},
57		{name: "breakout", value: "'JetBrains Mono'; color:red", want: false},
58	}
59
60	for _, tt := range tests {
61		t.Run(tt.name, func(t *testing.T) {
62			t.Parallel()
63
64			_, got := sanitizeCSSFontFamily(tt.value)
65			assert.Equal(t, tt.want, got)
66		})
67	}
68}
69
70func TestResolveShellTheme(t *testing.T) {
71	orig := currentPrefersDarkImpl
72	t.Cleanup(func() {
73		currentPrefersDarkImpl = orig
74	})
75
76	appearance := dto.WebUIAppearanceConfig{
77		SansFont:        "Inter",
78		SerifFont:       "Georgia",
79		MonospaceFont:   "JetBrains Mono",
80		DefaultFontSize: 16,
81		ColorScheme:     "default",
82		LightPalette: dto.ColorPalette{
83			Background:     "#ffffff",
84			Surface:        "#f8f8f8",
85			SurfaceVariant: "#eeeeee",
86			Text:           "#111111",
87			Muted:          "#666666",
88			Accent:         "#0055ff",
89			Border:         "#dddddd",
90		},
91		DarkPalette: dto.ColorPalette{
92			Background:     "#111111",
93			Surface:        "#1a1a1a",
94			SurfaceVariant: "#2a2a2a",
95			Text:           "#f5f5f5",
96			Muted:          "#a0a0a0",
97			Accent:         "#66aaff",
98			Border:         "#333333",
99		},
100	}
101
102	tests := []struct {
103		name               string
104		colorScheme        string
105		prefersDark        bool
106		wantClass          string
107		wantBackground     string
108		wantSurfaceVariant string
109	}{
110		{name: "prefer-dark", colorScheme: "prefer-dark", prefersDark: false, wantClass: "sv-dark", wantBackground: "#111111", wantSurfaceVariant: "#2a2a2a"},
111		{name: "prefer-light", colorScheme: "prefer-light", prefersDark: true, wantClass: "sv-light", wantBackground: "#ffffff", wantSurfaceVariant: "#eeeeee"},
112		{name: "default prefers dark", colorScheme: "default", prefersDark: true, wantClass: "sv-dark", wantBackground: "#111111", wantSurfaceVariant: "#2a2a2a"},
113		{name: "default prefers light", colorScheme: "default", prefersDark: false, wantClass: "sv-light", wantBackground: "#ffffff", wantSurfaceVariant: "#eeeeee"},
114	}
115
116	for _, tt := range tests {
117		t.Run(tt.name, func(t *testing.T) {
118			currentPrefersDarkImpl = func() bool { return tt.prefersDark }
119			ttAppearance := appearance
120			ttAppearance.ColorScheme = tt.colorScheme
121
122			got := resolveShellTheme(ttAppearance)
123
124			assert.Equal(t, tt.wantClass, got.RootClass)
125			assert.Contains(t, got.InlineVars, "--sv-background")
126			assert.Contains(t, got.InlineVars, "--sv-font-sans")
127			assert.Contains(t, got.InlineVars, tt.wantBackground)
128			assert.Contains(t, got.InlineVars, tt.wantSurfaceVariant)
129		})
130	}
131}

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