| 1 | package memoriatest |
| 2 | |
| 3 | import ( |
| 4 | "amadan.net/oficina/estilo.git/base" |
| 5 | "amadan.net/oficina/estilo.git/busy" |
| 6 | "amadan.net/oficina/estilo.git/chrome" |
| 7 | estilomenu "amadan.net/oficina/estilo.git/menu" |
| 8 | "amadan.net/oficina/estilo.git/sky" |
| 9 | "bytes" |
| 10 | "os" |
| 11 | "path/filepath" |
| 12 | "testing" |
| 13 | |
| 14 | "amadan.net/oficina/estilo.git/theme" |
| 15 | "amadan.net/rastrillo/rastrillo/ui" |
| 16 | ) |
| 17 | |
| 18 | // The scaffold delivered these files once; they are app-owned from |
| 19 | // then on. This test pins each vendored copy byte-identical to the |
| 20 | // library it came from: a reviewer runs the suite instead of reading |
| 21 | // ~56KB of assets as app diff, and a framework upgrade that forgets |
| 22 | // to re-copy is caught instead of drifting silently. If you edit one |
| 23 | // DELIBERATELY, delete its line below — the file is yours. |
| 24 | // |
| 25 | // tokens.css is the only one. select.js, datetime.js and calendar.js |
| 26 | // are rastrillo's too, but no page here links them, so they are not on |
| 27 | // disk (TestNoUnusedRastrilloScriptsOnDisk); rastrillo.js is estilo's. |
| 28 | func TestVendoredAssetsMatchTheLibrary(t *testing.T) { |
| 29 | for name, lib := range map[string][]byte{ |
| 30 | "tokens.css": ui.TokensCSS(), |
| 31 | } { |
| 32 | vendored, err := os.ReadFile(filepath.Join("..", "memoria", "static", name)) |
| 33 | if err != nil { |
| 34 | t.Errorf("read vendored %s: %v", name, err) |
| 35 | continue |
| 36 | } |
| 37 | if !bytes.Equal(vendored, lib) { |
| 38 | t.Errorf("static/%s differs from the library copy; re-copy it (or delete its pin if the edit was deliberate)", name) |
| 39 | } |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | // static/theme.css is Calma with Memoria's accent, from estilo (estilo |
| 44 | // AGENTS.md, decisions table, row 8). This pins it to the estilo this app |
| 45 | // is built against. After an estilo upgrade, re-write it with |
| 46 | // `go run ./scripts/writeestilo` — never by hand. |
| 47 | func TestThemeIsEstilosForMemoria(t *testing.T) { |
| 48 | want, ok := theme.ForApp("memoria") |
| 49 | if !ok { |
| 50 | t.Fatal("estilo's theme ring has no memoria") |
| 51 | } |
| 52 | got, err := os.ReadFile(filepath.Join("..", "memoria", "static", "theme.css")) |
| 53 | if err != nil { |
| 54 | t.Fatal(err) |
| 55 | } |
| 56 | if !bytes.Equal(got, want) { |
| 57 | t.Error("static/theme.css differs from estilo's theme for memoria; run go run ./scripts/writeestilo") |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // app.go mounts Onest at a literal pattern (the route gate reads |
| 62 | // literals); this keeps that literal equal to estilo's versioned prefix. |
| 63 | func TestFontRouteIsEstilosPrefix(t *testing.T) { |
| 64 | src, err := os.ReadFile(filepath.Join("..", "memoria", "app.go")) |
| 65 | if err != nil { |
| 66 | t.Fatal(err) |
| 67 | } |
| 68 | if !bytes.Contains(src, []byte(`"GET `+theme.FontPrefix+`"`)) { |
| 69 | t.Errorf("app.go does not mount Onest at %s", theme.FontPrefix) |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | // rastrillo's busy rule (Paul, 2026-09-29): a submit button's label gives |
| 74 | // way to a spinner for at least 650ms, the button disables, and Back |
| 75 | // resets it. static/ carries it as estilo does — the shim without the old |
| 76 | // rule, busy.js and busy.css — pinned here to the estilo this app builds |
| 77 | // against. After an estilo upgrade: go run ./scripts/writeestilo. |
| 78 | func TestBusyFilesAreEstilos(t *testing.T) { |
| 79 | for name, want := range map[string][]byte{ |
| 80 | "rastrillo.js": busy.ShimJS(), |
| 81 | "busy.js": busy.JS(), |
| 82 | "busy.css": busy.CSS(), |
| 83 | } { |
| 84 | got, err := os.ReadFile(filepath.Join("..", "memoria", "static", name)) |
| 85 | if err != nil { |
| 86 | t.Fatal(err) |
| 87 | } |
| 88 | if !bytes.Equal(got, want) { |
| 89 | t.Errorf("static/%s differs from estilo's; run go run ./scripts/writeestilo", name) |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // app.go mounts the sign-in door's sky at a literal pattern, for the same |
| 95 | // reason as the font: the route gate reads literals. This keeps it equal |
| 96 | // to estilo's versioned prefix, so a new sky version cannot leave the |
| 97 | // door linking layers nothing serves. |
| 98 | func TestSkyRouteIsEstilosPrefix(t *testing.T) { |
| 99 | src, err := os.ReadFile(filepath.Join("..", "memoria", "app.go")) |
| 100 | if err != nil { |
| 101 | t.Fatal(err) |
| 102 | } |
| 103 | if !bytes.Contains(src, []byte(`"GET `+sky.Prefix+`"`)) { |
| 104 | t.Errorf("app.go does not mount the sky at %s", sky.Prefix) |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | // The sign-in door (estilo chrome's estilo-signin and friends) is styled |
| 109 | // by chrome.css and sky.css, vendored into static/ so they are served |
| 110 | // fingerprinted like every other stylesheet. Pinned to the estilo this |
| 111 | // app builds against; after an upgrade: go run ./scripts/writeestilo. |
| 112 | func TestDoorStylesAreEstilos(t *testing.T) { |
| 113 | // And the rest of the shared chrome (round 2): links (base.css), the |
| 114 | // floating panels (menu.css) and chrome.js's Escape and outside-click |
| 115 | // close. |
| 116 | for name, want := range map[string][]byte{ |
| 117 | "chrome.css": chrome.CSS(), |
| 118 | "sky.css": sky.CSS(), |
| 119 | "base.css": base.CSS(), |
| 120 | "menu.css": estilomenu.CSS(), |
| 121 | "chrome.js": chrome.JS(), |
| 122 | } { |
| 123 | got, err := os.ReadFile(filepath.Join("..", "memoria", "static", name)) |
| 124 | if err != nil { |
| 125 | t.Fatal(err) |
| 126 | } |
| 127 | if !bytes.Equal(got, want) { |
| 128 | t.Errorf("static/%s differs from estilo's; run go run ./scripts/writeestilo", name) |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | // rastrillo doctor --fix re-copies what the library ships and the app |
| 134 | // lacks, and at 29e4491 that includes the old combined shim (with the |
| 135 | // old busy rule inside) as rastrillo.js, plus select.js, datetime.js and |
| 136 | // calendar.js. Memoria links none of the three, and its rastrillo.js is |
| 137 | // estilo's split shim (TestBusyFilesAreEstilos): the old shim loaded |
| 138 | // beside busy.js makes the two busy rules fight. After a --fix, run |
| 139 | // go run ./scripts/writeestilo and delete what it added; this fails |
| 140 | // until both are done. |
| 141 | func TestNoUnusedRastrilloScriptsOnDisk(t *testing.T) { |
| 142 | for _, name := range []string{"select.js", "datetime.js", "calendar.js"} { |
| 143 | if _, err := os.Stat(filepath.Join("..", "memoria", "static", name)); err == nil { |
| 144 | t.Errorf("static/%s is on disk; no page links it — delete it (rastrillo doctor --fix adds it)", name) |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | |