Public: anyone can read this; the crew can change it.

No page's first paint carries a red setting notice: a test fails one that does #10786

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/10777-no-page-s-first-paint-carries-a-red-sett → main 2 commits fork 3 threads · 3 unresolved

  • Merged. scout/10777-no-page-s-first-paint-carries-a-red-sett was merged into main as e66306b.

Review

Sign in to review this change.
1 file changed +94 −0
test/calm-no-banner.test.ts added +94
@@ -0,0 +1,94 @@
1 /**
2 * No first paint carries a standing notice for a configuration state (#10777).
3 * Home, the board and /flow, for an admin and an everyday account, with the
4 * forge in its most "configured" state (egress reported, not enforced), hold
5 * no alert/banner/danger-notice element and none of the setting's words.
6 * A setting is one line on its own page. Allowed kinds are named below: a
7 * transient flash (`data-flash`) and a real failure of a service, which says
8 * "down"/"failed" and is never a setting.
9 */
10 import { afterAll, beforeAll, expect, test } from "bun:test";
11 import { mkdtemp, rm } from "node:fs/promises";
12 import { tmpdir } from "node:os";
13 import { join } from "node:path";
14 import { createApp, type App } from "../src/app.ts";
15 import { serve, type Servers } from "../src/http/server.ts";
16 import { CAPACITY, CAPACITY_KEYS, capacityDefault, setCapacity, type CapacityKey } from "../src/core/capacity.ts";
17 import { createSession } from "../src/core/sessions.ts";
18 import { createUser } from "../src/core/users.ts";
19 import { EGRESS_REPORTED } from "../src/system/egress_line.ts";
20 import { seedAdmin } from "./helpers/admin.ts";
21
22 /**
23 * The kinds a first paint may carry, by the markup the app produces: a
outdated

data-flash and service-down do not exist in src/. Use the real markup: .toast (layout.ts:624) for a flash and the genuine failure elements. Also drop export.

24 * transient flash (`.toast`, role=status, layout.ts flashToast) and a real
25 * failure (`.toast` with data-tone="danger", role=alert, as account.ts and
26 * repo.ts render a refusal). Nothing else standing: no other role=alert, no
27 * banner / notice / alert class.
28 */
29 const ELEMENT = /<[a-z][^<>]*?(?:\brole="alert"|\bclass="[^"]*\b(?:banner|notice|notice-danger|notice-warn|alert)\b[^"]*")[^<>]*>/gi;
30 const allowed = (tag: string): boolean =>
31 /\bclass="[^"]*\btoast\b/.test(tag) && (/\brole="status"/.test(tag) || /\bdata-tone="danger"/.test(tag));
32 const standing = (body: string): string[] => [...body.matchAll(ELEMENT)].map((m) => m[0]).filter((t) => !allowed(t));
33
34 let dataDir = "";
35 let app: App, servers: Servers;
36 const cookies: Record<string, string> = {};
37 let adminId = "";
38
39 beforeAll(async () => {
40 dataDir = await mkdtemp(join(tmpdir(), "forge-nobanner-"));
41 app = createApp({ dataDir, port: 0 });
42 servers = await serve(app);
43 const admin = await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" });
44 if (!admin.ok) throw new Error(admin.error);
outdated

Only egress=report is set. Iterate the other configuration states too, or box 1 ("a configuration state") is only proven for one.

45 const bob = await createUser(app.db, { username: "bob", email: "[email protected]", password: "correct-horse-battery", origin: "seed", trusted: true });
46 if (!bob.ok) throw new Error(bob.error);
47 cookies.admin = `forge_session=${createSession(app.db, admin.value.id).token}`;
48 cookies.everyday = `forge_session=${createSession(app.db, bob.value.id).token}`;
49 adminId = admin.value.id;
50 });
51
52 afterAll(async () => {
53 servers.stop();
54 app.close();
55 await rm(dataDir, { recursive: true, force: true });
56 });
57
58 /** Every non-default value a setting can hold: each choice, or a count's ends. */
59 function otherValues(key: CapacityKey): string[] {
60 const kind = CAPACITY[key].kind;
61 const all = kind.type === "choice" ? [...kind.values] : [String(kind.min), String(kind.max)];
62 return all.filter((v) => v !== capacityDefault(key));
63 }
64
65 // Derived from the settings registry, so a new setting is covered at once.
66 for (const key of CAPACITY_KEYS) {
67 test(`${key} in every non-default state: home, board and /flow paint no setting banner`, async () => {
68 for (const value of otherValues(key)) {
outdated

Self-test uses the invented data-flash; feed it the app's real toast and a real failure so the allowed kinds are proven against actual markup.

69 setCapacity(app.db, key, value, adminId);
70 for (const who of ["admin", "everyday"]) {
71 for (const path of ["/", "/board", "/flow"]) {
72 const res = await fetch(`${servers.url}${path}`, { headers: { cookie: cookies[who]! }, redirect: "follow" });
73 // /flow is the admin's page: an everyday account is told it is not found, calmly.
74 expect(res.status).toBe(path === "/flow" && who === "everyday" ? 404 : 200);
75 const body = await res.text();
76 expect(body.length).toBeGreaterThan(100);
77 expect({ key, value, who, path, found: standing(body) }).toEqual({ key, value, who, path, found: [] });
78 // Secondary guard: the one sentence a setting once shouted.
79 expect(body).not.toContain(EGRESS_REPORTED.slice(0, 30));
80 }
81 }
82 }
83 setCapacity(app.db, key, null, adminId);
84 });
85 }
86
87 test("the detector fires on a banner and passes only the app's real flash and failure", () => {
88 const toast = (tone: string, role: string) => `<div class="toast" data-tone="${tone}" role="${role}">x</div>`;
89 expect(standing('<div class="banner">x</div>')).toHaveLength(1);
90 expect(standing('<p role="alert">egress</p>')).toHaveLength(1);
91 expect(standing('<div class="toast" data-tone="attn" role="alert">x</div>')).toHaveLength(1);
92 expect(standing(toast("success", "status"))).toEqual([]);
93 expect(standing(toast("danger", "alert"))).toEqual([]);
94 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs