package web
import (
"regexp"
"strings"
"testing"
)
// THE TYPE STAYS ON THE SCALE.
//
// The stylesheet had sixty-eight distinct font sizes, most between 11
// and 14 px, written one feature at a time; next to a mockup drawn on
// seven, every page read as crowded and flat. Sizes are now tokens
// (--fs-*), or ems relative to their parent. A new raw size fails here
// rather than quietly starting the drift again.
func TestTypeStaysOnTheScale(t *testing.T) {
sheet := paletteCSS + sheetLiteral
raw := regexp.MustCompile(`font(?:-size)?:[^;}]*?\b\d*\.?\d+(?:rem|px)\b`)
for _, m := range raw.FindAllString(sheet, -1) {
if strings.Contains(m, "--fs-") && !regexp.MustCompile(`\d(?:rem|px)`).MatchString(strings.ReplaceAll(m, "var(--fs-", "")) {
continue
}
t.Errorf("FAIL: %q is a raw size; use a --fs-* step or an em", m)
}
// Tokens themselves are declared once, in the measure block.
if n := strings.Count(sheet, "--fs-xs:"); n != 1 {
t.Errorf("FAIL: --fs-xs declared %d times", n)
}
}
// Every font-family is a token (or inherits), so the face is decided in
// one place and code never falls back to the browser's monospace.
func TestFontFamiliesAreTokens(t *testing.T) {
sheet := paletteCSS + sheetLiteral
for _, m := range regexp.MustCompile(`font:[^;}]*\b(ui-monospace|system-ui|monospace|sans-serif|serif)\b`).FindAllString(sheet, -1) {
if !strings.Contains(m, "var(--font-") {
t.Errorf("FAIL: shorthand %q names a face; use var(--font-sans) or var(--font-mono)", m)
}
}
for _, m := range regexp.MustCompile(`font-family:\s*([^;}]+)`).FindAllStringSubmatch(sheet, -1) {
v := strings.TrimSpace(m[1])
if strings.HasPrefix(v, "var(--font-") || v == "inherit" {
continue
}
t.Errorf("FAIL: font-family %q; use var(--font-sans) or var(--font-mono)", v)
}
}
// A weight with no face is synthesised or snapped by the browser, and
// differently by each; the stylesheet asks only for weights quarry
// serves a face for.
func TestWeightsHaveAFace(t *testing.T) {
served := map[string]bool{"normal": true, "bold": true, "inherit": true}
for _, f := range webFonts {
served[strings.TrimSpace(strings.Fields(string(rune('0'+f.weight/100)) + "00")[0])] = true
}
sheet := paletteCSS + sheetLiteral
for _, m := range regexp.MustCompile(`font-weight:\s*([0-9a-z]+)`).FindAllStringSubmatch(sheet, -1) {
if !served[m[1]] {
t.Errorf("FAIL: font-weight %s has no face; quarry serves 400, 500, 600 and 700", m[1])
}
}
if len(webFonts) == 0 || !strings.Contains(fontFaceCSS(), "font-display:swap") {
t.Error("FAIL: no @font-face, or one that can hold a page blank")
}
}
// A static chip is a label, not a control. The new-ticket form's chips
// are controls with a touch height; unscoped, that height stood every
// label chip on the site 36px tall (the ticket list's type tags).
func TestOnlyTheFormsChipsAreControls(t *testing.T) {
for _, m := range regexp.MustCompile(`(?m)^\.chip\s*\{[^}]*\}`).FindAllString(paletteCSS+sheetLiteral, -1) {
if strings.Contains(m, "min-height") || strings.Contains(m, "cursor: pointer") {
t.Errorf("FAIL: the site-wide .chip rule carries a control's size or cursor: %s", m)
}
}
}
// SPACE AND CORNERS STAY ON THEIR SCALES.
//
// The stylesheet had 187 distinct paddings and 32 radii; next to a
// mockup drawn on six gaps and two corners, the difference read as
// noise. Paddings and gaps are --sp-* steps and radii --r-* steps (a
// calc() may add them; 0, %, and em stay as they are).
func TestSpaceStaysOnTheScale(t *testing.T) {
sheet := paletteCSS + sheetLiteral
decl := regexp.MustCompile(`(?:^|[;{\s])((?:padding(?:-[a-z]+)?|margin(?:-[a-z]+)?|(?:row-|column-)?gap|border(?:-[a-z]+)*-radius))\s*:([^;}]*)`)
raw := regexp.MustCompile(`(?:^|[^\w.-])\d*\.?\d+(?:rem|px)\b`)
for _, m := range decl.FindAllStringSubmatch(sheet, -1) {
v := m[2]
if strings.Contains(v, "calc(") || strings.Contains(v, "max(") || strings.Contains(v, "min(") || strings.Contains(v, "clamp(") {
continue
}
if raw.MatchString(v) {
t.Errorf("FAIL: %s:%s is off the scale; use var(--sp-*) or var(--r-*)", m[1], v)
}
}
}