Files
selfpost/internal/web/view/static_test.go
T
mix 652f1fe438 panel: restyle on the mark's own palette and type
The stamp was approved on its own sheet and the panel kept the blue-on-cool-grey
it shipped with, so the mark read as pasted onto someone else's page. Brick
becomes the accent, the greys warm to paper, and the corners square up towards
the stamp's own edge.

Brick is two tokens rather than one. #7A3B2E under white text is 8.4:1, but as
text on the dark scheme's background it is about 2:1 — a link nobody can read.
So --accent-fill carries controls and --accent-text carries links and the active
navigation entry; in the light scheme they coincide, in the dark one they do not.

IBM Plex ships with the panel instead of being named and hoped for. The mark is
Plex converted to outlines, so a system stack left the panel's own name as the
only Plex on the page — and ui-monospace resolves to Consolas, SF Mono or DejaVu
Sans Mono depending on the machine, which laid the send log and mail.log tables
out differently for every operator. Three WOFF2 files, 76 KB, served from the
panel's own origin so default-src 'self' still covers them.

Two things changed shape rather than colour. Links had no rule at all and ran on
the browser's blue, invisible while the accent was blue and wrong the moment it
was not. And the column-narrowing that login and setup had by name now keys off
the narrow card itself, so Settings and the user form stop splitting their
heading from the card it belongs to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 00:24:17 +03:00

80 lines
2.8 KiB
Go

package view
import (
"net/http"
"net/http/httptest"
"testing"
)
// serveStatic runs one GET against the static handler.
func serveStatic(path string, headers map[string]string) *httptest.ResponseRecorder {
r := httptest.NewRequest(http.MethodGet, path, nil)
for k, v := range headers {
r.Header.Set(k, v)
}
rec := httptest.NewRecorder()
StaticHandler().ServeHTTP(rec, r)
return rec
}
// Every embedded asset must carry a validator. The favicon is the one that
// prompted this: a browser given no ETag and no Last-Modified caches it on a
// guess, and a rebranded panel keeps serving the old mark from the tab.
func TestStaticAssetsCarryETag(t *testing.T) {
for _, name := range []string{
"favicon.png", "favicon.svg", "panel.css", "panel.js", "htmx.min.js",
// The fonts are the assets this matters most for: they are the largest
// thing the panel serves and the ones a browser is most willing to keep.
"ibm-plex-sans.woff2", "ibm-plex-mono-400.woff2", "ibm-plex-mono-600.woff2",
} {
rec := serveStatic("/static/"+name, nil)
if rec.Code != http.StatusOK {
t.Fatalf("%s: got status %d, want 200", name, rec.Code)
}
if rec.Header().Get("ETag") == "" {
t.Errorf("%s: no ETag", name)
}
if got := rec.Header().Get("Cache-Control"); got != "no-cache" {
t.Errorf("%s: Cache-Control = %q, want %q", name, got, "no-cache")
}
}
}
// The point of the ETag is the cheap second request: the browser sends back
// what it has and gets a bodyless 304 when nothing changed.
func TestStaticETagRevalidates(t *testing.T) {
first := serveStatic("/static/favicon.png", nil)
etag := first.Header().Get("ETag")
if etag == "" {
t.Fatal("no ETag on the first response")
}
same := serveStatic("/static/favicon.png", map[string]string{"If-None-Match": etag})
if same.Code != http.StatusNotModified {
t.Errorf("matching If-None-Match: got status %d, want 304", same.Code)
}
if same.Body.Len() != 0 {
t.Errorf("matching If-None-Match: got %d bytes of body, want none", same.Body.Len())
}
// A stale validator — what a browser holds after the asset is replaced —
// has to produce the new bytes rather than another 304.
stale := serveStatic("/static/favicon.png", map[string]string{"If-None-Match": `"0000000000000000"`})
if stale.Code != http.StatusOK {
t.Errorf("stale If-None-Match: got status %d, want 200", stale.Code)
}
if stale.Body.Len() == 0 {
t.Error("stale If-None-Match: empty body, want the asset")
}
}
// Two different assets must not share a validator, or replacing one would be
// masked by the other's cached copy.
func TestStaticETagsAreContentDerived(t *testing.T) {
png := serveStatic("/static/favicon.png", nil).Header().Get("ETag")
svg := serveStatic("/static/favicon.svg", nil).Header().Get("ETag")
if png == svg {
t.Errorf("favicon.png and favicon.svg share the ETag %s", png)
}
}