package calendartest import ( "amadan.net/oficina/estilo.git/base" estilobusy "amadan.net/oficina/estilo.git/busy" "amadan.net/oficina/estilo.git/chrome" "amadan.net/oficina/estilo.git/menu" "amadan.net/oficina/estilo.git/sky" "amadan.net/oficina/estilo.git/theme" "bytes" "os" "path/filepath" "testing" "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, // Calma with Calendar's accent, from estilo (estilo AGENTS.md, // decisions table, row 8; this repo's ruling 2 is superseded). Pinned // to estilo instead, by TestThemeIsEstilosForCalendar below. "theme.css": true, // Calendar's OWN calendar.js (bring the working day into view on // load), not rastrillo's: from v0.26 rastrillo vendors a calendar.js // of its own, the month-grid overlay for date fields, under the same // name. Listed so doctor --fix never overwrites ours with it. Without // rastrillo's overlay, datetime.js's button opens the browser's own // picker, as it did on v0.23. Adopting the overlay means renaming // ours first. "calendar.js": true, // estilo's busy.js, not rastrillo's: from fb806a2 rastrillo's copy // learned to leave pow.js forms alone, and Calendar has no pow (auth // runs ProofOff). Pinned to estilo by TestBusyFilesAreEstilos, like // rastrillo.js; it follows estilo's when estilo takes the change. "busy.js": true, // rastrillo's console shell (from v0.27), which Calendar does not // draw: its bar and page frame are estilo's chrome. Never vendored, // so there is nothing here to pin. "shell.css": true, "shell.js": true, } // TestThemeIsEstilosForCalendar 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 TestThemeIsEstilosForCalendar(t *testing.T) { want, ok := theme.ForApp("calendar") if !ok { t.Fatal("estilo's theme ring has no calendar") } got, err := os.ReadFile(filepath.Join("..", "calendar", "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 calendar; 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("..", "calendar", "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) } } } // 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": estilobusy.ShimJS(), "busy.js": estilobusy.JS(), "busy.css": estilobusy.CSS(), } { got, err := os.ReadFile(filepath.Join("..", "calendar", "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) } } } // The sign-in door's styles (Paul, 2026-09-29: every app's sign-in is a // version of the Oficina sign-in screen): estilo chrome's chrome.css, // which holds the [estilo-door] and [estilo-signin] rules, and the sky // behind it. Pinned to the estilo this app builds against. After an // estilo upgrade: go run ./scripts/writeestilo. func TestDoorFilesAreEstilos(t *testing.T) { for name, want := range map[string][]byte{ "chrome.css": chrome.CSS(), "sky.css": sky.CSS(), } { got, err := os.ReadFile(filepath.Join("..", "calendar", "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) } } } // The shared chrome's other files (estilo spec 2026-09-23 §1): base.css // (the link rule), menu.css (every floating panel) and chrome.js (Esc and // outside-click close). The layout links them in the spec's order after // theme.css. Pinned like the door's; after an estilo upgrade: // go run ./scripts/writeestilo. func TestSharedChromeFilesAreEstilos(t *testing.T) { for name, want := range map[string][]byte{ "base.css": base.CSS(), "menu.css": menu.CSS(), "chrome.js": chrome.JS(), } { got, err := os.ReadFile(filepath.Join("..", "calendar", "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) } } }