package web
import (
"regexp"
"strings"
"testing"
)
// The stylesheet is served, not inlined. It used to be 112,118 bytes of
// <style> in every response — 40% of a 309,584-byte timeline page,
// byte-identical each time and uncacheable because it had no URL.
func TestSheetIsLinkedNotInlined(t *testing.T) {
if strings.Contains(baseHTML, "<style>") {
t.Error("the page still inlines a stylesheet")
}
if !strings.Contains(baseHTML, `rel="stylesheet"`) {
t.Error("the page does not link a stylesheet")
}
if len(sheet) < 50_000 {
t.Errorf("the sheet is %d bytes; the rules did not come with it", len(sheet))
}
}
// Both themes have to survive being moved out of the page — the whole
// two-theme story lives in this one file, and chroma's half of it is
// generated at startup rather than written down.
func TestSheetCarriesBothThemes(t *testing.T) {
for _, want := range []string{
"prefers-color-scheme: dark",
`:root[data-theme="dark"]`,
"--fg:", "--bg:", "--accent:",
".chroma",
} {
if !strings.Contains(sheet, want) {
t.Errorf("the sheet is missing %q", want)
}
}
}
// The URL carries a content hash so it can be served immutable: a rule
// change mints a new URL rather than asking anyone to revalidate.
func TestSheetURLIsContentAddressed(t *testing.T) {
if !strings.HasPrefix(sheetURL, "/assets/quarry.") || !strings.HasSuffix(sheetURL, ".css") {
t.Fatalf("sheetURL = %q", sheetURL)
}
if h := sheetHash(); !strings.Contains(sheetURL, h) {
t.Errorf("sheetURL %q does not carry the hash %q", sheetURL, h)
}
// Same content, same hash — the URL must be stable across a
// restart or every deploy busts every reader's cache for nothing.
if sheetHash() != sheetHash() {
t.Error("the hash is not stable for identical content")
}
}
// No page may carry its own <style> element.
//
// The strict CSP is style-src 'self' with no nonce for style elements —
// style-src-attr covers the style="" attribute and nothing else — so a
// <style> block in a template is kept in the DOM, given no sheet, and
// applied to nothing. It fails silently: no console error a reader
// would notice, no failing test, just a page rendering unstyled.
//
// It happened. Chat and the inbox each carried one, and shipped
// unstyled from the day the CSP landed until somebody looked: the chat
// log rendered as a numbered list because .chatlog's list-style never
// arrived. Every test asserted the rules were in the HTML, which they
// were, and none asserted they were anywhere a browser would read them.
func TestNoTemplateCarriesItsOwnStyleElement(t *testing.T) {
if n := strings.Count(baseHTML, "<style"); n > 0 {
t.Errorf("base carries %d <style> elements; the CSP drops them", n)
}
for _, src := range []struct{ name, body string }{
{"chatLogSource", chatLogSource},
{"boardSource", boardSource},
{"exploreSource", exploreSource},
{"fleetAdminSource", fleetAdminSource},
{"runStepsSource", runStepsSource},
{"ticketSource", ticketSource},
} {
if strings.Contains(src.body, "<style") {
t.Errorf("%s carries a <style> element; put the rules in sheetLiteral, "+
"which is served, hashed and actually applied", src.name)
}
}
}
// The rules chat and the inbox need are IN the served sheet. Pinned by
// name because moving them there is what made them work at all.
func TestSheetCarriesChatAndInboxRules(t *testing.T) {
for _, want := range []string{
".chatlog{", ".chatmsg", ".chat-toolbar{", ".chat-quote{",
".composer{", ".chat-date{", ".chat-divider",
".inbox-bar{", ".attn-row", ".attn-done{",
} {
if !strings.Contains(sheet, want) {
t.Errorf("the served stylesheet is missing %q", want)
}
}
// And the log is not a numbered list.
if !strings.Contains(sheet, "list-style:none") {
t.Error("the chat log has no list-style reset; it will render as 1. 2. 3.")
}
}
// The served sheet carries no comments and no indentation: the literal
// is written to be read, the bytes on the wire are not. The rule count
// is untouched, and no string in the sheet ever contained a comment
// marker for the stripper to eat.
func TestSheetShipsLean(t *testing.T) {
if strings.Contains(sheet, "/*") || strings.Contains(sheet, "{{") {
t.Fatal("the served sheet still carries comments or template fences")
}
full := rawSheet
// Comments talk about braces too, so the reference is the sheet
// with comments removed by a second, independent method.
ref := regexp.MustCompile(`(?s)/\*.*?\*/`).ReplaceAllString(full, "")
ref = strings.ReplaceAll(strings.ReplaceAll(ref, "{{-", ""), "-}}", "")
if strings.Count(leanCSS(full), "{") != strings.Count(ref, "{") || strings.Count(leanCSS(full), "}") != strings.Count(ref, "}") {
t.Fatal("stripping changed the rule structure")
}
if strings.Contains(full, `"/*`) || strings.Contains(full, `'/*`) || strings.Contains(full, `*/"`) {
t.Fatal("a string in the sheet holds a comment marker; the stripper is no longer safe")
}
if lean := leanCSS(full); len(lean) > len(full)*2/3 {
t.Fatalf("lean sheet is %d of %d bytes; expected to lose at least a third", len(lean), len(full))
}
for i, line := range strings.Split(sheet, "\n") {
if line != strings.TrimSpace(line) {
t.Fatalf("indentation survived on line %d: %q", i+1, line)
}
}
}