carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "io/fs"
5 "regexp"
6 "strings"
7 "testing"
8)
9
10// The switcher's chrome is drawn, not typed: the disclosure caret and the
11// menu's marks are inline SVG from icons.go, and the text glyphs they replace
12// ("▾", "✓") are gone from the header. A glyph is whatever the reader's font
13// decides it is — a caret that renders as a black triangle on one machine and
14// a coloured emoji on another is not a control, and neither size nor colour
15// obeys the page. An icon does both.
16func TestSwitcherChromeIsInlineSVGNotTextGlyphs(t *testing.T) {
17 fx := newConsoleFixture(t)
18 c := signInPaul(t, fx)
19 _, _, body := get(t, c, fx.u("/"))
20 h := header(t, body)
21
22 summary, _, ok := strings.Cut(h, "</summary>")
23 if !ok {
24 t.Fatalf("no <summary> in the header:\n%s", h)
25 }
26 // The trigger's caret: a real <svg>, in the vendored idiom, hidden from
27 // assistive tech because the account name beside it is the label.
28 if !strings.Contains(summary, "<svg") {
29 t.Errorf("the switcher trigger has no inline <svg> caret:\n%s", summary)
30 }
31 for _, want := range []string{`viewBox="0 0 24 24"`, `stroke="currentColor"`, `aria-hidden="true"`} {
32 if !strings.Contains(summary, want) {
33 t.Errorf("the caret svg is missing %s:\n%s", want, summary)
34 }
35 }
36 // The menu marks the current account and offers a new one, both with
37 // icons rather than a reused tick character.
38 if n := strings.Count(h, "<svg"); n < 3 {
39 t.Errorf("header has %d inline icons, want the caret plus a mark per menu row:\n%s", n, h)
40 }
41 // No chrome glyph survives anywhere in the header. Text arrows, ticks and
42 // crosses are exactly what the icon set exists to replace.
43 for _, glyph := range []string{"▾", "▼", "▽", "⌄", "✓", "✔", "✕", "✖", "×", "→", "←"} {
44 if strings.Contains(h, glyph) {
45 t.Errorf("header still uses the text glyph %q as chrome:\n%s", glyph, h)
46 }
47 }
48}
49
50// Emoji are content, never chrome (Eleven's docs/icons.md). The footer's
51// robot is a factor-X disclosure — a mark this platform puts on LLM-written
52// text everywhere, from README.md to site/platform — so it is content and it
53// stays. This test pins that distinction so a later sweep of "remove the
54// emoji" cannot quietly take the disclosure with it.
55func TestFactorXDisclosureKeepsItsEmoji(t *testing.T) {
56 fx := newConsoleFixture(t)
57 c := signInPaul(t, fx)
58 _, _, body := get(t, c, fx.u("/"))
59 foot, _, ok := strings.Cut(body, "</footer>")
60 if !ok {
61 t.Fatalf("no <footer> in:\n%s", body)
62 }
63 if !strings.Contains(foot, "🤖") || !strings.Contains(foot, "written by an LLM") {
64 t.Errorf("the footer lost its factor-X disclosure:\n%s", foot)
65 }
66}
67
68// Same-origin, the console's version. internal/status asserts this on its
69// published page with a flat "no http(s):// anywhere" ban; the console cannot
70// use that ban verbatim because it legitimately LINKS out — the footer's
71// 11factor.org citation, an app's canary host. A link is navigation the
72// reader chooses. What must never appear is a SUBRESOURCE: anything the
73// browser fetches on its own while rendering the page — a script, a
74// stylesheet, a font, an image, an @import, a CSS url(). The console ships
75// one HTML document with its CSS inline and its icons inline, and that is the
76// whole of it. Vendoring the icons is what keeps this true.
77func TestConsoleTemplatesFetchNothingOffOrigin(t *testing.T) {
78 banned := []string{
79 "<script src", // an external script, the usual way an icon CDN arrives
80 "<link ", // stylesheets, fonts, preconnects, prefetches
81 "src=", // no <img>, <iframe> or <source> of any origin
82 "@import", // a stylesheet importing another
83 "url(http", // a CSS-referenced asset...
84 "url(//", // ...and the protocol-relative spelling of one
85 `href="//`, // protocol-relative, dodging the "http" ban entirely
86 "cdn.", // any CDN host, spelled anywhere at all
87 "lucide.dev", // the icons are vendored; the source URL lives in a Go comment
88 "unpkg",
89 "jsdelivr",
90 "googleapis",
91 }
92 err := fs.WalkDir(tmplFS, ".", func(path string, d fs.DirEntry, err error) error {
93 if err != nil || d.IsDir() {
94 return err
95 }
96 b, err := tmplFS.ReadFile(path)
97 if err != nil {
98 return err
99 }
100 for _, bad := range banned {
101 if strings.Contains(string(b), bad) {
102 t.Errorf("%s must fetch nothing off-origin; found %q", path, bad)
103 }
104 }
105 return nil
106 })
107 if err != nil {
108 t.Fatal(err)
109 }
110}
111
112var iconCall = regexp.MustCompile(`{{\s*icon\s+"([^"]*)"\s*}}`)
113
114// icon returns "" for an unknown slug so a typo cannot 500 a page mid-render.
115// This is the test that turns that empty string back into a failure, at build
116// time, where a missing caret is cheap to fix.
117func TestTemplateIconSlugsAllExist(t *testing.T) {
118 used := 0
119 err := fs.WalkDir(tmplFS, ".", func(path string, d fs.DirEntry, err error) error {
120 if err != nil || d.IsDir() {
121 return err
122 }
123 b, err := tmplFS.ReadFile(path)
124 if err != nil {
125 return err
126 }
127 for _, m := range iconCall.FindAllStringSubmatch(string(b), -1) {
128 used++
129 if icon(m[1]) == "" {
130 t.Errorf("%s calls {{icon %q}}, which is not in icons.go", path, m[1])
131 }
132 }
133 return nil
134 })
135 if err != nil {
136 t.Fatal(err)
137 }
138 if used == 0 {
139 t.Error("no template uses {{icon}} — the chrome went back to text glyphs")
140 }
141}
142
143// Every vendored icon is one self-contained, colour-inheriting, screen-reader
144// -silent 24×24 SVG. Getting any of these wrong is invisible in review and
145// obvious on the page: a hardcoded fill ignores dark mode, a stray absolute
146// URL reintroduces the request vendoring exists to avoid.
147func TestVendoredIconsAreSelfContained(t *testing.T) {
148 if len(icons) == 0 {
149 t.Fatal("the icon set is empty")
150 }
151 for slug, svg := range icons {
152 s := string(svg)
153 for _, want := range []string{
154 "<svg", "</svg>", `viewBox="0 0 24 24"`,
155 `stroke="currentColor"`, `aria-hidden="true"`, `class="icon"`,
156 } {
157 if !strings.Contains(s, want) {
158 t.Errorf("icon %q is missing %s: %s", slug, want, s)
159 }
160 }
161 for _, bad := range []string{"http://", "https://", "<image", "xlink:href", "url("} {
162 if strings.Contains(s, bad) {
163 t.Errorf("icon %q reaches outside the page (%q): %s", slug, bad, s)
164 }
165 }
166 // No width/height of their own: they size from the .icon CSS rule so
167 // one icon at a 0.9rem menu row and the same icon in body text are the
168 // same shape at two sizes, not two hand-tuned copies. (The leading
169 // space is what keeps stroke-width, a different thing entirely, out
170 // of this check.)
171 if strings.Contains(s, " width=") || strings.Contains(s, " height=") {
172 t.Errorf("icon %q hardcodes a size instead of sizing from the text: %s", slug, s)
173 }
174 }
175}
176