D0 Part 1: vendored fonts + Lucide sprite + setup CSS fix
- Vendor Plus Jakarta Sans + JetBrains Mono as variable woff2 (latin + latin-ext) under internal/web/static/fonts/, embedded via go:embed and served at /static/fonts/ (font/woff2, immutable cache). Google Fonts @import replaced with @font-face rules preserving unicode-range — removes the CDN dependency that silently broke on offline nodes. - Add templates/icons.html: vendored Lucide sprite (30 icons, symbol ids i-<name>), included at the top of <body> in layout.html. - Fix setup wizard handleCSS: serve the embedded web.StyleCSS() instead of a dataDir-derived filesystem path that never exists in the container (production setup silently served minimalCSS). Fallback to minimalCSS only if the embedded read errors, with a WARN log. - Tests: font route + StyleCSS accessor (web), Scenario E embedded-CSS test (setup; red-proven against the pre-fix handler).
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestServeFontHandler asserts the vendored woff2 files are embedded and served
|
||||
// with the right content type + immutable caching (design system v2, D0 Part 1.1).
|
||||
func TestServeFontHandler(t *testing.T) {
|
||||
s := &Server{}
|
||||
for _, name := range []string{"pjs-latin.woff2", "pjs-latin-ext.woff2", "jbm-latin.woff2", "jbm-latin-ext.woff2"} {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/static/fonts/"+name, nil)
|
||||
s.serveFontHandler(rec, req, name)
|
||||
if rec.Code != 200 {
|
||||
t.Errorf("%s: status = %d, want 200", name, rec.Code)
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); ct != "font/woff2" {
|
||||
t.Errorf("%s: Content-Type = %q, want font/woff2", name, ct)
|
||||
}
|
||||
if cc := rec.Header().Get("Cache-Control"); !strings.Contains(cc, "immutable") {
|
||||
t.Errorf("%s: Cache-Control = %q, want immutable", name, cc)
|
||||
}
|
||||
// woff2 magic number: "wOF2"
|
||||
if body := rec.Body.Bytes(); len(body) < 4 || string(body[:4]) != "wOF2" {
|
||||
t.Errorf("%s: body is not a woff2 file (len=%d)", name, len(body))
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown font → 404, and path traversal is neutralized by filepath.Base.
|
||||
rec := httptest.NewRecorder()
|
||||
s.serveFontHandler(rec, httptest.NewRequest("GET", "/static/fonts/nope.woff2", nil), "nope.woff2")
|
||||
if rec.Code != 404 {
|
||||
t.Errorf("unknown font: status = %d, want 404", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStyleCSSAccessor asserts the exported accessor returns the embedded
|
||||
// stylesheet (consumed by the setup wizard) with the vendored font-face rules
|
||||
// and no CDN import.
|
||||
func TestStyleCSSAccessor(t *testing.T) {
|
||||
data, err := StyleCSS()
|
||||
if err != nil {
|
||||
t.Fatalf("StyleCSS() error: %v", err)
|
||||
}
|
||||
css := string(data)
|
||||
if len(css) < 10000 {
|
||||
t.Fatalf("StyleCSS() length = %d, want > 10000", len(css))
|
||||
}
|
||||
if !strings.Contains(css, "@font-face") || !strings.Contains(css, "/static/fonts/pjs-latin.woff2") {
|
||||
t.Error("StyleCSS() missing vendored @font-face rules")
|
||||
}
|
||||
if strings.Contains(css, "fonts.googleapis.com") {
|
||||
t.Error("StyleCSS() still references the Google Fonts CDN")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user