oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package guards
2
3import (
4 "os"
5 "path/filepath"
6 "regexp"
7 "sort"
8 "strings"
9 "testing"
10
11 "amadan.net/oficina/estilo.git/base"
12 "amadan.net/oficina/estilo.git/chrome"
13 estilomenu "amadan.net/oficina/estilo.git/menu"
14 "amadan.net/oficina/estilo.git/theme"
15)
16
17// ourStylesheets are the CSS files this app writes. The vendored ones
18// are not checked: they define their own custom properties and know
19// nothing about rastrillo's.
20var ourStylesheets = []string{
21 "internal/sheets/static/sheets.css",
22}
23
24// tokenSources are the files that DEFINE rastrillo's tokens: the
25// structural ones copied by `rastrillo doctor` and the shared palette.
26var tokenSources = []string{
27 "internal/sheets/static/tokens.css",
28 "Estilo theme module",
29}
30
31var (
32 tokenUse = regexp.MustCompile(`var\(\s*(--rst-[a-zA-Z0-9-]+)`)
33 tokenDefine = regexp.MustCompile(`(--rst-[a-zA-Z0-9-]+)\s*:`)
34)
35
36// TestEveryTokenWeUseExists is the check that would have caught five
37// invented token names shipping to a canary.
38//
39// CSS resolves an undefined custom property to its fallback without a
40// warning anywhere — no console message, no parse error, nothing in a
41// Go test. So `var(--rst-border, 1px solid #ccc)` renders a plausible
42// page that is simply not themed, and the only way to notice is to look
43// at it in both schemes and know what you expected. That is not a gate.
44//
45// The five were --rst-border, --rst-text-mut, --rst-warning,
46// --rst-font-mono and --rst-shell-bar-h. The real names are
47// --rst-line, --rst-text-muted, --rst-tone-warning-fg and --rst-font;
48// the last had no equivalent at all and the layout was measuring
49// something the framework never published.
50func TestEveryTokenWeUseExists(t *testing.T) {
51 root := repoRoot(t)
52
53 defined := map[string]bool{}
54 for _, rel := range tokenSources {
55 var body []byte
56 var err error
57 if rel == "Estilo theme module" {
58 body = theme.CSS()
59 } else {
60 body, err = os.ReadFile(filepath.Join(root, rel))
61 }
62 if err != nil {
63 t.Fatalf("guards: read %s: %v — the token sources are what `rastrillo doctor` copies; "+
64 "if one moved, this test needs updating, not deleting", rel, err)
65 }
66 for _, m := range tokenDefine.FindAllStringSubmatch(string(body), -1) {
67 defined[m[1]] = true
68 }
69 }
70 if len(defined) == 0 {
71 t.Fatal("guards: found no --rst-* definitions at all; this test is not checking anything")
72 }
73
74 var used int
75 for _, rel := range ourStylesheets {
76 body, err := os.ReadFile(filepath.Join(root, rel))
77 if err != nil {
78 t.Fatalf("guards: read %s: %v", rel, err)
79 }
80 var missing []string
81 for _, m := range tokenUse.FindAllStringSubmatch(string(body), -1) {
82 used++
83 if !defined[m[1]] {
84 missing = append(missing, m[1])
85 }
86 }
87 sort.Strings(missing)
88 for _, name := range dedupe(missing) {
89 t.Errorf("%s: var(%s) is not defined in %s — CSS resolves an unknown "+
90 "custom property to its fallback in silence, so this renders a page "+
91 "that is simply not themed",
92 rel, name, strings.Join(tokenSources, " or "))
93 }
94 }
95 if used == 0 {
96 t.Fatal("guards: our stylesheets reference no tokens at all; either they stopped " +
97 "using the design system or this test is looking in the wrong place")
98 }
99}
100
101func dedupe(in []string) []string {
102 var out []string
103 for i, s := range in {
104 if i == 0 || s != in[i-1] {
105 out = append(out, s)
106 }
107 }
108 return out
109}
110
111// TestEveryEstiloPropertyWeUseExists is the same check for the estilo
112// custom properties sheets.css reads (--estilo-float-radius and the
113// rest), against the three stylesheets that define them. The same
114// failure as a misspelled --rst- token: CSS takes the fallback in
115// silence, so a float drawn with an estilo property nobody defines is
116// simply not the suite's float.
117func TestEveryEstiloPropertyWeUseExists(t *testing.T) {
118 defined := map[string]bool{}
119 def := regexp.MustCompile(`(--estilo-[a-zA-Z0-9-]+)\s*:`)
120 for _, css := range [][]byte{base.CSS(), estilomenu.CSS(), chrome.CSS()} {
121 for _, m := range def.FindAllStringSubmatch(string(css), -1) {
122 defined[m[1]] = true
123 }
124 }
125 if len(defined) == 0 {
126 t.Fatal("guards: estilo defines no --estilo-* properties; this test is reading the wrong stylesheets")
127 }
128 use := regexp.MustCompile(`var\(\s*(--estilo-[a-zA-Z0-9-]+)`)
129 for _, rel := range ourStylesheets {
130 body := readRepoFile(t, repoRoot(t), rel)
131 for _, m := range use.FindAllStringSubmatch(body, -1) {
132 if !defined[m[1]] {
133 t.Errorf("%s: var(%s) is not defined by estilo's base, menu or chrome stylesheet", rel, m[1])
134 }
135 }
136 }
137}
138