| 1 | package calendartest |
| 2 | |
| 3 | import ( |
| 4 | "amadan.net/oficina/estilo.git/base" |
| 5 | estilobusy "amadan.net/oficina/estilo.git/busy" |
| 6 | "amadan.net/oficina/estilo.git/chrome" |
| 7 | "amadan.net/oficina/estilo.git/menu" |
| 8 | "amadan.net/oficina/estilo.git/sky" |
| 9 | "amadan.net/oficina/estilo.git/theme" |
| 10 | "bytes" |
| 11 | "os" |
| 12 | "path/filepath" |
| 13 | "testing" |
| 14 | |
| 15 | "amadan.net/rastrillo/rastrillo/ui" |
| 16 | ) |
| 17 | |
| 18 | // vendoredTheme is the theme this app was scaffolded with. Change it |
| 19 | // here when you swap static/theme.css for another of ui.ThemeNames(). |
| 20 | const vendoredTheme = "day" |
| 21 | |
| 22 | // vendoredIsMine names the vendored files this app has edited |
| 23 | // DELIBERATELY. Add one here when you mean to diverge: this test stops |
| 24 | // pinning it, and rastrillo doctor leaves it alone too. The file was |
| 25 | // yours from the moment the scaffold wrote it — this is only where you |
| 26 | // record that the edit was on purpose, so the next reader (and the next |
| 27 | // upgrade) can tell it from drift. |
| 28 | var vendoredIsMine = map[string]bool{ |
| 29 | // rastrillo's shim as estilo carries it: without the old busy rule, |
| 30 | // which would fight busy.js. Pinned to estilo by TestBusyFilesAreEstilos. |
| 31 | "rastrillo.js": true, |
| 32 | // Calma with Calendar's accent, from estilo (estilo AGENTS.md, |
| 33 | // decisions table, row 8; this repo's ruling 2 is superseded). Pinned |
| 34 | // to estilo instead, by TestThemeIsEstilosForCalendar below. |
| 35 | "theme.css": true, |
| 36 | // Calendar's OWN calendar.js (bring the working day into view on |
| 37 | // load), not rastrillo's: from v0.26 rastrillo vendors a calendar.js |
| 38 | // of its own, the month-grid overlay for date fields, under the same |
| 39 | // name. Listed so doctor --fix never overwrites ours with it. Without |
| 40 | // rastrillo's overlay, datetime.js's button opens the browser's own |
| 41 | // picker, as it did on v0.23. Adopting the overlay means renaming |
| 42 | // ours first. |
| 43 | "calendar.js": true, |
| 44 | // estilo's busy.js, not rastrillo's: from fb806a2 rastrillo's copy |
| 45 | // learned to leave pow.js forms alone, and Calendar has no pow (auth |
| 46 | // runs ProofOff). Pinned to estilo by TestBusyFilesAreEstilos, like |
| 47 | // rastrillo.js; it follows estilo's when estilo takes the change. |
| 48 | "busy.js": true, |
| 49 | // rastrillo's console shell (from v0.27), which Calendar does not |
| 50 | // draw: its bar and page frame are estilo's chrome. Never vendored, |
| 51 | // so there is nothing here to pin. |
| 52 | "shell.css": true, |
| 53 | "shell.js": true, |
| 54 | } |
| 55 | |
| 56 | // TestThemeIsEstilosForCalendar pins static/theme.css to the estilo this |
| 57 | // app is built against. After an estilo upgrade, re-write it with |
| 58 | // `go run ./scripts/writeestilo` — never by hand. |
| 59 | func TestThemeIsEstilosForCalendar(t *testing.T) { |
| 60 | want, ok := theme.ForApp("calendar") |
| 61 | if !ok { |
| 62 | t.Fatal("estilo's theme ring has no calendar") |
| 63 | } |
| 64 | got, err := os.ReadFile(filepath.Join("..", "calendar", "static", "theme.css")) |
| 65 | if err != nil { |
| 66 | t.Fatal(err) |
| 67 | } |
| 68 | if !bytes.Equal(got, want) { |
| 69 | t.Error("static/theme.css differs from estilo's theme for calendar; run go run ./scripts/writeestilo") |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | // The scaffold delivered these files once; they are app-owned from |
| 74 | // then on. This test pins each vendored copy byte-identical to the |
| 75 | // library it came from: a reviewer runs the suite instead of reading |
| 76 | // ~56KB of assets as app diff, and a framework upgrade that forgets |
| 77 | // to re-copy is caught instead of drifting silently. |
| 78 | // |
| 79 | // The list is ui.VendoredAssets — the same function rastrillo new |
| 80 | // wrote these files from and rastrillo doctor compares them against — |
| 81 | // so a file added to the library's vendored set is pinned here without |
| 82 | // this file changing. |
| 83 | func TestVendoredAssetsMatchTheLibrary(t *testing.T) { |
| 84 | assets, ok := ui.VendoredAssets(vendoredTheme) |
| 85 | if !ok { |
| 86 | t.Fatalf("unknown theme %q", vendoredTheme) |
| 87 | } |
| 88 | for name, lib := range assets { |
| 89 | if vendoredIsMine[name] { |
| 90 | continue |
| 91 | } |
| 92 | vendored, err := os.ReadFile(filepath.Join("..", "calendar", "static", name)) |
| 93 | if err != nil { |
| 94 | t.Errorf("read vendored %s: %v", name, err) |
| 95 | continue |
| 96 | } |
| 97 | if !bytes.Equal(vendored, lib) { |
| 98 | 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) |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | // rastrillo's busy rule (Paul, 2026-09-29): a submit button's label gives |
| 104 | // way to a spinner for at least 650ms, the button disables, and Back |
| 105 | // resets it. static/ carries it as estilo does — the shim without the old |
| 106 | // rule, busy.js and busy.css — pinned here to the estilo this app builds |
| 107 | // against. After an estilo upgrade: go run ./scripts/writeestilo. |
| 108 | func TestBusyFilesAreEstilos(t *testing.T) { |
| 109 | for name, want := range map[string][]byte{ |
| 110 | "rastrillo.js": estilobusy.ShimJS(), |
| 111 | "busy.js": estilobusy.JS(), |
| 112 | "busy.css": estilobusy.CSS(), |
| 113 | } { |
| 114 | got, err := os.ReadFile(filepath.Join("..", "calendar", "static", name)) |
| 115 | if err != nil { |
| 116 | t.Fatal(err) |
| 117 | } |
| 118 | if !bytes.Equal(got, want) { |
| 119 | t.Errorf("static/%s differs from estilo's; run go run ./scripts/writeestilo", name) |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | // The sign-in door's styles (Paul, 2026-09-29: every app's sign-in is a |
| 125 | // version of the Oficina sign-in screen): estilo chrome's chrome.css, |
| 126 | // which holds the [estilo-door] and [estilo-signin] rules, and the sky |
| 127 | // behind it. Pinned to the estilo this app builds against. After an |
| 128 | // estilo upgrade: go run ./scripts/writeestilo. |
| 129 | func TestDoorFilesAreEstilos(t *testing.T) { |
| 130 | for name, want := range map[string][]byte{ |
| 131 | "chrome.css": chrome.CSS(), |
| 132 | "sky.css": sky.CSS(), |
| 133 | } { |
| 134 | got, err := os.ReadFile(filepath.Join("..", "calendar", "static", name)) |
| 135 | if err != nil { |
| 136 | t.Fatal(err) |
| 137 | } |
| 138 | if !bytes.Equal(got, want) { |
| 139 | t.Errorf("static/%s differs from estilo's; run go run ./scripts/writeestilo", name) |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | // The shared chrome's other files (estilo spec 2026-09-23 §1): base.css |
| 145 | // (the link rule), menu.css (every floating panel) and chrome.js (Esc and |
| 146 | // outside-click close). The layout links them in the spec's order after |
| 147 | // theme.css. Pinned like the door's; after an estilo upgrade: |
| 148 | // go run ./scripts/writeestilo. |
| 149 | func TestSharedChromeFilesAreEstilos(t *testing.T) { |
| 150 | for name, want := range map[string][]byte{ |
| 151 | "base.css": base.CSS(), |
| 152 | "menu.css": menu.CSS(), |
| 153 | "chrome.js": chrome.JS(), |
| 154 | } { |
| 155 | got, err := os.ReadFile(filepath.Join("..", "calendar", "static", name)) |
| 156 | if err != nil { |
| 157 | t.Fatal(err) |
| 158 | } |
| 159 | if !bytes.Equal(got, want) { |
| 160 | t.Errorf("static/%s differs from estilo's; run go run ./scripts/writeestilo", name) |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |