1package theme
2
3import (
4 "regexp"
5 "strings"
6 "testing"
7
8 "github.com/stretchr/testify/assert"
9)
10
11func TestGenerateCSS_UsesGTKFontForNativeWidgets(t *testing.T) {
12 css := GenerateCSSWithScaleAndFonts(DefaultDarkPalette(), 1.0, FontConfig{
13 SansFont: "Fira Sans",
14 MonospaceFont: "Fira Code",
15 GtkFont: "Adwaita Sans",
16 })
17
18 assert.Contains(t, css, `--font-sans: "Fira Sans", sans-serif;`)
19 assert.Contains(t, css, `--font-mono: "Fira Code", monospace;`)
20 assert.Contains(t, css, `--font-gtk: "Adwaita Sans", sans-serif;`)
21 assert.Contains(t, css, "window, tooltip, popover {\n\tfont-family: var(--font-gtk);\n}")
22 assert.NotContains(t, css, "window, tooltip, popover {\n\tfont-family: var(--font-sans);\n}")
23}
24
25func TestGenerateCSS_StandaloneOmniboxWindowUsesTransparentBackground(t *testing.T) {
26 css := GenerateCSS(DefaultDarkPalette())
27
28 if !strings.Contains(css, "window.standalone-omnibox-window") {
29 t.Fatalf("expected standalone omnibox window selector in CSS, got %q", css)
30 }
31 re := regexp.MustCompile(`window\.standalone-omnibox-window\s*\{\s*background-color:\s*transparent\s*;?\s*\}`)
32 if !re.MatchString(css) {
33 t.Fatalf("expected standalone omnibox window background to be transparent")
34 }
35}
36
37func TestGenerateCSS_OmniboxHeaderIsOpaque(t *testing.T) {
38 css := GenerateCSS(DefaultDarkPalette())
39
40 if !strings.Contains(css, ".omnibox-header {") {
41 t.Fatalf("expected omnibox header selector in CSS")
42 }
43 if !strings.Contains(css, "background-color: var(--surface);") {
44 t.Fatalf("expected omnibox header to use an opaque surface background")
45 }
46 if !strings.Contains(css, "background-image: none;") {
47 t.Fatalf("expected omnibox header gradient to be disabled")
48 }
49}
50
51func TestGenerateCSS_OmniboxHeaderBadgeSelectorsExist(t *testing.T) {
52 css := GenerateCSS(DefaultDarkPalette())
53
54 if !strings.Contains(css, ".omnibox-header-badge {") {
55 t.Fatalf("expected omnibox header badge selector in CSS")
56 }
57 if !strings.Contains(css, ".omnibox-header-badge:hover {") {
58 t.Fatalf("expected omnibox header badge hover selector in CSS")
59 }
60}
61
62func TestGenerateCSS_OmniboxHeaderBadgeUsesPassiveNeutralChipStyling(t *testing.T) {
63 css := GenerateCSS(DefaultDarkPalette())
64
65 badgeRe := regexp.MustCompile(`(?s)\.omnibox-header-badge\s*\{[^}]*background-color:\s*alpha\(var\(--surface-variant\),\s*0\.65\);[^}]*color:\s*var\(--muted\);[^}]*border:\s*0\.0625em solid alpha\(var\(--border\),\s*0\.85\);[^}]*border-radius:\s*0\.1875em;[^}]*padding:\s*0\.0625em 0\.375em;[^}]*font-size:\s*0\.6875em;`)
66 if !badgeRe.MatchString(css) {
67 t.Fatalf("expected omnibox header badge to use passive neutral chip styling")
68 }
69
70 hoverRe := regexp.MustCompile(`(?s)\.omnibox-header-badge:hover\s*\{[^}]*background-color:\s*shade\(var\(--surface-variant\),\s*1\.08\);[^}]*border-color:\s*var\(--border\);[^}]*color:\s*var\(--text\);`)
71 if !hoverRe.MatchString(css) {
72 t.Fatalf("expected omnibox header badge hover styling to stay neutral while hinting interactivity")
73 }
74
75 badgeBlock := regexp.MustCompile(`(?s)\.omnibox-header-badge\s*\{[^}]*\}`).FindString(css)
76 if strings.Contains(badgeBlock, "background-color: alpha(var(--accent)") {
77 t.Fatalf("expected omnibox header badge resting state to avoid accent-based styling")
78 }
79 if strings.Contains(badgeBlock, "color: var(--accent)") {
80 t.Fatalf("expected omnibox header badge resting text to avoid accent color")
81 }
82}
83
84func TestGenerateCSS_FavoriteRowsOnlyTintTrailingAffordance(t *testing.T) {
85 css := GenerateCSS(DefaultDarkPalette())
86
87 if !strings.Contains(css, ".omnibox-row.omnibox-row-favorite {") {
88 t.Fatalf("expected favorite row selector in CSS")
89 }
90 if !strings.Contains(css, "border-left: 0.1875em solid var(--warning);") {
91 t.Fatalf("expected favorite rows to mark the left edge with the warning color")
92 }
93 if strings.Contains(css, "border-right: 0.125em solid alpha(var(--warning), 0.55);") {
94 t.Fatalf("expected favorite indicator to move off the right edge")
95 }
96 if !strings.Contains(css, "color: mix(var(--warning), var(--muted), 0.45);") {
97 t.Fatalf("expected favorite star color to be softened")
98 }
99 if strings.Contains(css, ".omnibox-row.omnibox-row-favorite .omnibox-favorite-star-slot") {
100 t.Fatalf("expected favorite highlight to move off the star slot")
101 }
102}
103
104func TestGenerateCSS_OmniboxSearchAreaMatchesHeaderSurface(t *testing.T) {
105 css := GenerateCSS(DefaultDarkPalette())
106
107 containerRe := regexp.MustCompile(`(?s)\.omnibox-container\s*\{[^}]*background-color:\s*var\(--surface\);`)
108 if !containerRe.MatchString(css) {
109 t.Fatalf("expected omnibox container to use the header surface color")
110 }
111 entryRe := regexp.MustCompile(`(?s)entry\.omnibox-entry\s*,\s*entry\.omnibox-entry\s*>\s*text\s*\{[^}]*background-color:\s*alpha\(var\(--bg\),\s*0\.88\);[^}]*background-image:\s*none;`)
112 if !entryRe.MatchString(css) {
113 t.Fatalf("expected omnibox entry and its text node to use the darker background fill")
114 }
115 focusedEntryRe := regexp.MustCompile(`(?s)entry\.omnibox-entry:focus[^\{]*,\s*entry\.omnibox-entry:focus-within[^\{]*,\s*entry\.omnibox-entry:focus-visible[^\{]*,\s*entry\.omnibox-entry:focus\s*>\s*text[^\{]*,\s*entry\.omnibox-entry:focus-within\s*>\s*text[^\{]*,\s*entry\.omnibox-entry:focus-visible\s*>\s*text\s*\{[^}]*background-color:\s*shade\(var\(--bg\),\s*1\.05\);`)
116 if !focusedEntryRe.MatchString(css) {
117 t.Fatalf("expected focused omnibox entry text node to keep the darker bg-based fill")
118 }
119 scrolledRe := regexp.MustCompile(`(?s)\.omnibox-scrolled\s*\{[^}]*background-color:\s*var\(--surface\);`)
120 if !scrolledRe.MatchString(css) {
121 t.Fatalf("expected omnibox results area to use the header surface color")
122 }
123}
124
125func TestGenerateCSS_ContextMenuUsesCompactSingleFrameStyling(t *testing.T) {
126 css := GenerateCSS(DefaultDarkPalette())
127
128 assert.Contains(t, css, "popover.context-menu-popover > contents {")
129 assert.Contains(t, css, ".context-menu {")
130 assert.Contains(t, css, ".context-menu button.context-menu-item {")
131 assert.Contains(t, css, ".context-menu button.context-menu-item:hover,")
132 assert.Contains(t, css, ".context-menu button.context-menu-item:disabled {")
133 assert.Contains(t, css, ".context-menu-separator {")
134
135 popoverBlockRe := regexp.MustCompile(`(?s)popover\.context-menu-popover\s*>\s*contents\s*\{[^}]*\}`)
136 popoverBlock := popoverBlockRe.FindString(css)
137 if popoverBlock == "" {
138 t.Fatal("expected context menu popover contents block in CSS")
139 }
140
141 assert.Contains(t, popoverBlock, "background-color: var(--surface);")
142 assert.Contains(t, popoverBlock, "border: 0.0625em solid alpha(var(--border), 0.85);")
143 assert.Contains(t, popoverBlock, "border-radius: 0.25em;")
144 assert.Contains(t, popoverBlock, "box-shadow:")
145
146 blockRe := regexp.MustCompile(`(?s)\.context-menu\s*\{[^}]*\}`)
147 block := blockRe.FindString(css)
148 if block == "" {
149 t.Fatal("expected context menu block in CSS")
150 }
151
152 assert.NotContains(t, block, "background-color:")
153 assert.Contains(t, block, "padding: 0.125em 0;")
154 assert.Contains(t, block, "min-width: 10.5em;")
155 assert.NotContains(t, block, "border:")
156 assert.NotContains(t, block, "border-radius:")
157 assert.NotContains(t, block, "box-shadow:")
158 assert.NotContains(t, block, "#")
159 assert.NotContains(t, block, "black")
160 assert.NotContains(t, block, "white")
161
162 itemBlockRe := regexp.MustCompile(`(?s)\.context-menu button\.context-menu-item\s*\{[^}]*\}`)
163 itemBlock := itemBlockRe.FindString(css)
164 if itemBlock == "" {
165 t.Fatal("expected context menu item block in CSS")
166 }
167
168 assert.Contains(t, itemBlock, "padding: 0.25em 0.75em;")
169 assert.Contains(t, itemBlock, "min-height: 1.75em;")
170}
171
172func TestGenerateCSSWithScale_DoesNotEmitRootTextScaling(t *testing.T) {
173 css := GenerateCSSWithScale(DefaultDarkPalette(), 1.3)
174
175 assert.NotContains(t, css, "/* UI Scaling (130%) */")
176 assert.NotContains(t, css, "window {\n\tfont-size:")
177}
178
179func TestGenerateCSSWithScale_DoesNotEmitLineHeightOverrides(t *testing.T) {
180 css := GenerateCSSWithScale(DefaultDarkPalette(), 1.3)
181
182 assert.NotContains(t, css, "line-height:")
183 assert.NotContains(t, css, "label,\nentry,\nentry > text {\n\tline-height:")
184}
185
186func TestGenerateCSSWithScale_UsesOpaqueOmniboxRowBaseBackground(t *testing.T) {
187 css := GenerateCSSWithScale(DefaultDarkPalette(), 1.3)
188
189 assert.Contains(t, css, ".omnibox-row {")
190 assert.Contains(t, css, "background-color: var(--surface);")
191 assert.NotContains(t, css, ".omnibox-row {\n\tpadding: 0.5em 0.75em;\n\tmargin: 0;\n\tborder-radius: 0;\n\tborder-left: 0.1875em solid transparent;\n\tborder-bottom: 0.0625em solid alpha(var(--border), 0.35);\n\tbackground-color: transparent;")
192}
193
194func TestGenerateCSSWithScale_UsesMediumWeightForOmniboxSuggestionTitle(t *testing.T) {
195 css := GenerateCSSWithScale(DefaultDarkPalette(), 1.3)
196
197 assert.Contains(t, css, ".omnibox-suggestion-title {")
198 assert.Contains(t, css, "font-weight: 500;")
199 assert.NotContains(t, css, ".omnibox-suggestion-title {\n\tfont-size: 0.875em;\n\tcolor: var(--text);\n\tfont-weight: 400;")
200}
201
202func TestGenerateCSS_ContentAreaTabBarInset(t *testing.T) {
203 css := GenerateCSS(DefaultDarkPalette())
204
205 topBlock := cssRuleBlock(t, css, ".content-area-tabbar-inset-top")
206 if !strings.Contains(topBlock, "padding-top: 2em;") {
207 t.Fatal("expected padding-top: 2em in .content-area-tabbar-inset-top block")
208 }
209 bottomBlock := cssRuleBlock(t, css, ".content-area-tabbar-inset-bottom")
210 if !strings.Contains(bottomBlock, "padding-bottom: 2em;") {
211 t.Fatal("expected padding-bottom: 2em in .content-area-tabbar-inset-bottom block")
212 }
213 if !strings.Contains(css, ".tab-bar") {
214 t.Fatal("expected .tab-bar selector in generated CSS")
215 }
216 if !strings.Contains(css, "min-height: 2em;") {
217 t.Fatal("expected min-height: 2em in .tab-bar block")
218 }
219}
220
221func cssRuleBlock(t *testing.T, css, selector string) string {
222 t.Helper()
223 re := regexp.MustCompile(regexp.QuoteMeta(selector) + `\s*\{([^}]*)\}`)
224 matches := re.FindStringSubmatch(css)
225 if len(matches) != 2 {
226 t.Fatalf("expected %s selector in generated CSS", selector)
227 }
228 return matches[1]
229}
230
231func TestGenerateCSS_DoesNotEmitUnsupportedGTKProperties(t *testing.T) {
232 css := GenerateCSS(DefaultDarkPalette())
233
234 assert.NotContains(t, css, "pointer-events:")
235 assert.NotContains(t, css, "text-align:")
236}