rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1package fiesta
2
3import (
4 "bytes"
5 "io/fs"
6 "slices"
7 "sort"
8 "strings"
9 "testing"
10)
11
12// The root package is the design system's files, embedded, behind a
13// handful of accessors. These tests hold the accessors to the tree:
14// every name a list returns is a file that is there, every file that
15// is there is a name a list returns, and what an accessor hands back
16// is the same bytes a caller would read from FS().
17
18// readFS reads one path from FS(), failing the test when the tree
19// is not there at all rather than letting a nil fs.FS panic the
20// binary and hide every other test's verdict.
21func readFS(t *testing.T, path string) []byte {
22 t.Helper()
23 tree := FS()
24 if tree == nil {
25 t.Fatalf("FS() is nil; the embedded tree is missing")
26 }
27 b, err := fs.ReadFile(tree, path)
28 if err != nil {
29 t.Fatalf("FS() has no %s: %v", path, err)
30 }
31 return b
32}
33
34// listFS names the files under one directory of FS(), base names
35// only, sorted.
36func listFS(t *testing.T, dir string) []string {
37 t.Helper()
38 tree := FS()
39 if tree == nil {
40 t.Fatalf("FS() is nil; the embedded tree is missing")
41 }
42 entries, err := fs.ReadDir(tree, dir)
43 if err != nil {
44 t.Fatalf("FS() has no directory %s: %v", dir, err)
45 }
46 var names []string
47 for _, e := range entries {
48 if !e.IsDir() {
49 names = append(names, e.Name())
50 }
51 }
52 sort.Strings(names)
53 return names
54}
55
56func TestTheStylesheetsAreEmbedded(t *testing.T) {
57 for _, tc := range []struct {
58 name string
59 got []byte
60 path string
61 }{
62 {"TokensCSS", TokensCSS(), "css/tokens.css"},
63 {"ShellCSS", ShellCSS(), "css/shell.css"},
64 } {
65 if len(tc.got) == 0 {
66 t.Errorf("%s() is empty", tc.name)
67 continue
68 }
69 if want := readFS(t, tc.path); !bytes.Equal(tc.got, want) {
70 t.Errorf("%s() differs from FS()'s %s", tc.name, tc.path)
71 }
72 }
73}
74
75func TestEveryThemeIsEmbedded(t *testing.T) {
76 want := []string{"day", "plain", "signal"}
77 if got := ThemeNames(); !slices.Equal(got, want) {
78 t.Fatalf("ThemeNames() = %q, want %q", got, want)
79 }
80 for _, name := range want {
81 css, ok := ThemeCSS(name)
82 if !ok {
83 t.Errorf("ThemeCSS(%q) reports no such theme", name)
84 continue
85 }
86 if !bytes.Contains(css, []byte(":root")) {
87 t.Errorf("ThemeCSS(%q) has no :root block", name)
88 }
89 if embedded := readFS(t, "css/themes/"+name+".css"); !bytes.Equal(css, embedded) {
90 t.Errorf("ThemeCSS(%q) differs from FS()'s css/themes/%s.css", name, name)
91 }
92 }
93 if _, ok := ThemeCSS("nope"); ok {
94 t.Errorf("ThemeCSS(%q) reports a theme that is not shipped", "nope")
95 }
96 var files []string
97 for _, f := range listFS(t, "css/themes") {
98 files = append(files, strings.TrimSuffix(f, ".css"))
99 }
100 if !slices.Equal(files, want) {
101 t.Errorf("css/themes/ holds %q, but ThemeNames() lists %q", files, want)
102 }
103}
104
105func TestEveryScriptIsEmbedded(t *testing.T) {
106 want := []string{"fiesta.js", "busy.js", "light.js", "anchor.js", "shell.js", "transition.js", "copy.js", "select.js", "datetime.js", "calendar.js"}
107 if got := ScriptNames(); !slices.Equal(got, want) {
108 t.Fatalf("ScriptNames() = %q, want %q", got, want)
109 }
110 for _, name := range want {
111 js, ok := Script(name)
112 if !ok {
113 t.Errorf("Script(%q) reports no such script", name)
114 continue
115 }
116 if len(js) == 0 {
117 t.Errorf("Script(%q) is empty", name)
118 }
119 if embedded := readFS(t, "js/"+name); !bytes.Equal(js, embedded) {
120 t.Errorf("Script(%q) differs from FS()'s js/%s", name, name)
121 }
122 }
123 if _, ok := Script("nope.js"); ok {
124 t.Errorf("Script(%q) reports a script that is not shipped", "nope.js")
125 }
126 files := listFS(t, "js")
127 sorted := slices.Clone(want)
128 sort.Strings(sorted)
129 if !slices.Equal(files, sorted) {
130 t.Errorf("js/ holds %q, but ScriptNames() lists %q", files, want)
131 }
132}
133
134func TestEveryLanguageIsEmbedded(t *testing.T) {
135 want := []string{"en", "ar", "bn", "es", "ga", "hi", "ja", "pt-PT", "ru", "vi", "yue", "zh-Hans"}
136 if got := Languages(); !slices.Equal(got, want) {
137 t.Fatalf("Languages() = %q, want %q", got, want)
138 }
139 for _, lang := range want {
140 b, ok := Strings(lang)
141 if !ok {
142 t.Errorf("Strings(%q) reports no such language", lang)
143 continue
144 }
145 if len(b) == 0 {
146 t.Errorf("Strings(%q) is empty", lang)
147 }
148 if embedded := readFS(t, "i18n/"+lang+".json"); !bytes.Equal(b, embedded) {
149 t.Errorf("Strings(%q) differs from FS()'s i18n/%s.json", lang, lang)
150 }
151 }
152 // pt is the test alias, never a file; a miss is false, not a panic.
153 for _, bad := range []string{"pt", "nope", "../LICENSE"} {
154 if _, ok := Strings(bad); ok {
155 t.Errorf("Strings(%q) reports a language that is not shipped", bad)
156 }
157 }
158 files := listFS(t, "i18n")
159 var sorted []string
160 for _, lang := range want {
161 sorted = append(sorted, lang+".json")
162 }
163 sort.Strings(sorted)
164 if !slices.Equal(files, sorted) {
165 t.Errorf("i18n/ holds %q, but Languages() lists %q", files, want)
166 }
167}
168
169func TestEveryIconIsOneLucideSVG(t *testing.T) {
170 want := []string{
171 "alert-triangle", "check", "check-circle", "chevron-down",
172 "help-circle", "info", "kebab", "menu", "plus", "search", "x", "x-circle",
173 }
174 got := IconSlugs()
175 if len(got) != 12 {
176 t.Errorf("IconSlugs() lists %d slugs, want 12: %q", len(got), got)
177 }
178 if !sort.StringsAreSorted(got) {
179 t.Errorf("IconSlugs() is not sorted: %q", got)
180 }
181 if !slices.Equal(got, want) {
182 t.Fatalf("IconSlugs() = %q, want %q", got, want)
183 }
184 const open = `<svg class="icon" viewBox="0 0 24 24"`
185 for _, slug := range want {
186 svg, ok := Icon(slug)
187 if !ok {
188 t.Errorf("Icon(%q) reports no such icon", slug)
189 continue
190 }
191 s := string(svg)
192 if !strings.HasPrefix(s, open) {
193 t.Errorf("Icon(%q) does not start %s: %.60q", slug, open, s)
194 }
195 if !strings.HasSuffix(s, "</svg>") {
196 t.Errorf("Icon(%q) does not end </svg>: %q", slug, s)
197 }
198 if strings.Count(s, "<svg") != 1 {
199 t.Errorf("Icon(%q) holds %d <svg> elements, want 1", slug, strings.Count(s, "<svg"))
200 }
201 if strings.Contains(s, "xmlns") {
202 t.Errorf("Icon(%q) carries an xmlns; the inline form has none", slug)
203 }
204 if embedded := readFS(t, "icons/"+slug+".svg"); !bytes.Equal(svg, embedded) {
205 t.Errorf("Icon(%q) differs from FS()'s icons/%s.svg", slug, slug)
206 }
207 }
208 if _, ok := Icon("nope"); ok {
209 t.Errorf("Icon(%q) reports an icon that is not shipped", "nope")
210 }
211 // Sorted after the suffix comes off: "check-circle.svg" sorts
212 // before "check.svg", but "check" sorts before "check-circle".
213 var files []string
214 for _, f := range listFS(t, "icons") {
215 if strings.HasSuffix(f, ".svg") {
216 files = append(files, strings.TrimSuffix(f, ".svg"))
217 }
218 }
219 sort.Strings(files)
220 if !slices.Equal(files, want) {
221 t.Errorf("icons/ holds %q, but IconSlugs() lists %q", files, want)
222 }
223}
224
225func TestNamesAreCopies(t *testing.T) {
226 for _, tc := range []struct {
227 name string
228 list func() []string
229 }{
230 {"ThemeNames", ThemeNames},
231 {"ScriptNames", ScriptNames},
232 {"Languages", Languages},
233 {"IconSlugs", IconSlugs},
234 } {
235 first := tc.list()
236 if len(first) == 0 {
237 t.Errorf("%s() is empty", tc.name)
238 continue
239 }
240 first[0] = "scribbled"
241 if again := tc.list(); again[0] == "scribbled" {
242 t.Errorf("%s() hands out its own slice: a caller's write came back", tc.name)
243 }
244 }
245}
246
247func TestTheLicenseIsMPL(t *testing.T) {
248 const first = "Mozilla Public License Version 2.0"
249 lic := License()
250 if !bytes.HasPrefix(lic, []byte(first)) {
251 t.Errorf("License() does not start %q: %.40q", first, lic)
252 }
253 if embedded := readFS(t, "LICENSE"); !bytes.Equal(lic, embedded) {
254 t.Errorf("License() differs from FS()'s LICENSE")
255 }
256 // Lucide's licence is two notices: ISC for Lucide, and Feather's
257 // MIT for the glyphs Lucide took from it, which is ten of these
258 // twelve. Both must travel with the files.
259 lucide := readFS(t, "icons/LICENSE-lucide.txt")
260 // The MIT strings are the notice's own heading, copyright line and
261 // grant, not bare "MIT" and "Cole Bemis": a sentence that merely
262 // mentions Feather (MIT) and names its author carries both of those
263 // without carrying the licence.
264 for _, want := range []string{
265 "Lucide", "ISC", "Permission to use, copy, modify, and/or distribute",
266 "The MIT License (MIT)", "Copyright (c) 2013-present Cole Bemis",
267 "Permission is hereby granted, free of charge",
268 } {
269 if !bytes.Contains(lucide, []byte(want)) {
270 t.Errorf("icons/LICENSE-lucide.txt does not mention %q", want)
271 }
272 }
273}
274
275func TestTheVersionIsSemver(t *testing.T) {
276 if Version != "1.2.0" {
277 t.Errorf("Version = %q, want %q", Version, "1.2.0")
278 }
279}
280