package slidestest import ( "amadan.net/oficina/estilo.git/busy" "bytes" "fmt" "os" "path/filepath" "regexp" "sort" "testing" "amadan.net/oficina/estilo.git/theme" "amadan.net/rastrillo/rastrillo/ui" ) // vendoredTheme is the theme this app was scaffolded with. Change it // here when you swap static/theme.css for another of ui.ThemeNames(). const vendoredTheme = "day" // vendoredIsMine names the vendored files this app has edited // DELIBERATELY. Add one here when you mean to diverge: this test stops // pinning it, and rastrillo doctor leaves it alone too. The file was // yours from the moment the scaffold wrote it — this is only where you // record that the edit was on purpose, so the next reader (and the next // upgrade) can tell it from drift. var vendoredIsMine = map[string]bool{ // rastrillo's shim as estilo carries it: without the old busy rule, // which would fight busy.js. Pinned to estilo by TestBusyFilesAreEstilos. "rastrillo.js": true, // estilo's busy.js, for the same reason: the busy rule as the suite // carries it. ui's own copy moved on at rastrillo fb806a2 (pow.js // arming) and estilo's has not, so the two can no longer both be // pinned. Pinned to estilo by TestBusyFilesAreEstilos. "busy.js": true, // Calma with Slides' accent, from estilo (estilo AGENTS.md, decisions // table, row 8) rather than rastrillo's day. Pinned to estilo instead, // by TestThemeIsEstilosForSlides below. "theme.css": true, } // TestThemeIsEstilosForSlides pins static/theme.css to the estilo this app // is built against. After an estilo upgrade, re-write it with // `go run ./scripts/writeestilo` — never by hand. func TestThemeIsEstilosForSlides(t *testing.T) { want, ok := theme.ForApp("slides") if !ok { t.Fatal("estilo's theme ring has no slides") } got, err := os.ReadFile(filepath.Join("..", "slides", "static", "theme.css")) if err != nil { t.Fatal(err) } if !bytes.Equal(got, want) { t.Error("static/theme.css differs from estilo's theme for slides; run go run ./scripts/writeestilo") } } // The scaffold delivered these files once; they are app-owned from // then on. This test pins each vendored copy byte-identical to the // library it came from: a reviewer runs the suite instead of reading // ~56KB of assets as app diff, and a framework upgrade that forgets // to re-copy is caught instead of drifting silently. // // The list is ui.VendoredAssets — the same function rastrillo new // wrote these files from and rastrillo doctor compares them against — // so a file added to the library's vendored set is pinned here without // this file changing. func TestVendoredAssetsMatchTheLibrary(t *testing.T) { assets, ok := ui.VendoredAssets(vendoredTheme) if !ok { t.Fatalf("unknown theme %q", vendoredTheme) } for name, lib := range assets { if vendoredIsMine[name] { continue } vendored, err := os.ReadFile(filepath.Join("..", "slides", "static", name)) if err != nil { t.Errorf("read vendored %s: %v", name, err) continue } if !bytes.Equal(vendored, lib) { t.Errorf("static/%s differs from the library copy; re-copy it with rastrillo doctor --fix (or add it to vendoredIsMine if the edit was deliberate)", name) } } } // TestEveryRastrilloTokenSlidesUsesExists makes a misspelled design token a // test failure instead of a browser falling back silently. The vocabulary is // the committed tokens.css; usages include both authored templates and CSS. func TestEveryRastrilloTokenSlidesUsesExists(t *testing.T) { declared := map[string]bool{} definitions := append(append([]byte{}, ui.TokensCSS()...), theme.CSS()...) for _, match := range regexp.MustCompile(`--(rst-[a-z0-9-]+)\s*:`).FindAllSubmatch(definitions, -1) { declared[string(match[1])] = true } if len(declared) == 0 { t.Fatal("tokens.css declares no --rst-* tokens") } files, err := filepath.Glob(filepath.Join("..", "slides", "templates", "*.html")) if err != nil { t.Fatal(err) } css, err := filepath.Glob(filepath.Join("..", "slides", "static", "*.css")) if err != nil { t.Fatal(err) } for _, file := range css { // tokens.css is rastrillo's vendored component implementation; // TestVendoredAssetsMatchTheLibrary pins it. This gate is for // Slides-authored CSS and inline template styles. if filepath.Base(file) != "tokens.css" { files = append(files, file) } } used := map[string]bool{} pattern := regexp.MustCompile(`var\(--(rst-[a-z0-9-]+)`) comments := regexp.MustCompile(`(?s)/\*.*?\*/`) for _, file := range files { body, err := os.ReadFile(file) if err != nil { t.Fatal(err) } for _, match := range pattern.FindAllSubmatch(comments.ReplaceAll(body, nil), -1) { used[string(match[1])] = true } } var missing []string for token := range used { if !declared[token] { missing = append(missing, token) } } sort.Strings(missing) for _, token := range missing { t.Error(fmt.Sprintf("Slides uses --%s, but tokens.css does not declare it", token)) } } // rastrillo's busy rule (Paul, 2026-09-29): a submit button's label gives // way to a spinner for at least 650ms, the button disables, and Back // resets it. static/ carries it as estilo does — the shim without the old // rule, busy.js and busy.css — pinned here to the estilo this app builds // against. After an estilo upgrade: go run ./scripts/writeestilo. func TestBusyFilesAreEstilos(t *testing.T) { for name, want := range map[string][]byte{ "rastrillo.js": busy.ShimJS(), "busy.js": busy.JS(), "busy.css": busy.CSS(), } { got, err := os.ReadFile(filepath.Join("..", "slides", "static", name)) if err != nil { t.Fatal(err) } if !bytes.Equal(got, want) { t.Errorf("static/%s differs from estilo's; run go run ./scripts/writeestilo", name) } } }