Public: anyone can read this; the crew can change it.
A forge preview's check reports the held page's owner looks, desktop and phone, with how many screens tall each is #9510
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/9508-a-forge-preview-s-check-reports-the-held → main 1 commit fork
2 files
changed +96
−0
docs/features/vitals-screens.md +2
| @@ -8,3 +8,5 @@ A check's per-look vitals say how many screens tall each look is (`scrollHeight | ||
| 8 | 8 | Report-only: neither can turn a check red. Skipped looks show no screens. |
| 9 | 9 | |
| 10 | 10 | API: `GET /api/v1/repos/:owner/:name/commits/:oid/check` and `GET /api/v1/repos/:owner/:name/pulls/:number/checks` carry `looks: [{look, screens, sideways}]`, from the same file as the run page (a run with no file of its own falls back to the commit's filed looks). `screens` is the one-decimal string (`""` without a size), `sideways` is a boolean; `[]` when there are none or the reader may not read runs. |
| 11 | ||
| 12 | The held page (`/preview-admin/sample/held`) is measured as an owner look (`PREVIEW_OWNER_LOOKS`), not a visitor look, because the visitor cap (`LOOKS_MAX`, 24) is spent; the check lists it at desktop and phone with its screens (#9508). |
test/check-looks-held.test.ts added +94
| @@ -0,0 +1,94 @@ | ||
| 1 | /** A forge preview's check reports the held page as an owner look, desktop and phone, with screens tall (#9508). */ | |
| 2 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 3 | import { mkdirSync, writeFileSync } from "node:fs"; | |
| 4 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 5 | import { tmpdir } from "node:os"; | |
| 6 | import { join } from "node:path"; | |
| 7 | import { createApp, type App } from "../src/app.ts"; | |
| 8 | import { commitToBranch, createRepo } from "../src/actions/repos.ts"; | |
| 9 | import { PREVIEW_HELD_PAGE, PREVIEW_OWNER_LOOKS } from "../src/spawn/preview-looks.ts"; | |
| 10 | import { looksPath } from "../src/checks/looks.ts"; | |
| 11 | import { claimRun, createRun, finishRun } from "../src/core/runs.ts"; | |
| 12 | import { createSession } from "../src/core/sessions.ts"; | |
| 13 | import { createUser } from "../src/core/users.ts"; | |
| 14 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 15 | import { quietLogs } from "./helpers/globals.ts"; | |
| 16 | ||
| 17 | quietLogs(); | |
| 18 | ||
| 19 | let dataDir: string; | |
| 20 | let app: App; | |
| 21 | let servers: Servers; | |
| 22 | let cookie = ""; | |
| 23 | let repoId = ""; | |
| 24 | const cells = process.env["FORGE_CELLS"]; | |
| 25 | const OID = "b".repeat(40); | |
| 26 | ||
| 27 | const get = async (path: string) => { | |
| 28 | const res = await fetch(`${servers.url}${path}`, { headers: { cookie } }); | |
| 29 | return (await res.json()) as any; | |
| 30 | }; | |
| 31 | ||
| 32 | beforeAll(async () => { | |
| 33 | process.env["FORGE_CELLS"] = "off"; | |
| 34 | dataDir = await mkdtemp(join(tmpdir(), "forge-check-looks-api-")); | |
| 35 | app = createApp({ dataDir, port: 0 }); | |
| 36 | servers = await serve(app); | |
| 37 | const made = await createUser(app.db, { | |
| 38 | origin: "seed", | |
| 39 | trusted: true, | |
| 40 | username: "alice", | |
| 41 | email: "[email protected]", | |
| 42 | password: "a-good-passphrase", | |
| 43 | adminIfFirst: true, | |
| 44 | }); | |
| 45 | if (!made.ok) throw new Error(String(made.error)); | |
| 46 | const alice = made.value; | |
| 47 | cookie = `forge_session=${createSession(app.db, alice.id).token}`; | |
| 48 | const repo = await createRepo(app, alice, { name: "r", visibility: "public" }); | |
| 49 | if (!repo.ok) throw new Error(String(repo.error)); | |
| 50 | repoId = repo.value.id; | |
| 51 | for (const [branch, file] of [[undefined, "forge.json"], ["topic", "t.txt"]] as const) { | |
| 52 | const done = await commitToBranch(app, alice, repo.value, { | |
| 53 | ...(branch === undefined ? {} : { branch }), | |
| 54 | message: "change", | |
| 55 | files: [{ path: file, content: file === "forge.json" ? "{}" : "x" }], | |
| 56 | }); | |
| 57 | if (!done.ok) throw new Error(JSON.stringify(done.error)); | |
| 58 | } | |
| 59 | }); | |
| 60 | ||
| 61 | afterAll(async () => { | |
| 62 | await servers.stop(); | |
| 63 | app.close(); | |
| 64 | if (cells === undefined) delete process.env["FORGE_CELLS"]; | |
| 65 | else process.env["FORGE_CELLS"] = cells; | |
| 66 | await rm(dataDir, { recursive: true, force: true }); | |
| 67 | }); | |
| 68 | ||
| 69 | const lookName = (page: string, kind: string) => (page === "/" ? kind : `${kind} ${page}`); | |
| 70 | ||
| 71 | test("a forge preview's check lists the held page at desktop and phone with screens tall", async () => { | |
| 72 | const held = PREVIEW_OWNER_LOOKS.filter((l) => l.page === PREVIEW_HELD_PAGE); | |
| 73 | expect(held.map((l) => l.kind).sort()).toEqual(["desktop", "phone"]); | |
| 74 | const run = createRun(app.db, { repoId, kind: "check", commitOid: OID, env: "main", branch: "main" }); | |
| 75 | claimRun(app.db, run.id); | |
| 76 | finishRun(app.db, run.id, { state: "ok", summary: "ok" }); | |
| 77 | const path = looksPath(dataDir, run.id); | |
| 78 | mkdirSync(join(path, ".."), { recursive: true }); | |
| 79 | const size = { scrollWidth: 390, scrollHeight: 2000, innerWidth: 390, innerHeight: 800 }; | |
| 80 | writeFileSync( | |
| 81 | path, | |
| 82 | JSON.stringify( | |
| 83 | PREVIEW_OWNER_LOOKS.map((l) => ({ look: lookName(l.page, l.kind), entries: {}, size })), | |
| 84 | ), | |
| 85 | ); | |
| 86 | const body = await get(`/api/v1/repos/alice/r/commits/${OID}/check`); | |
| 87 | for (const kind of ["desktop", "phone"]) { | |
| 88 | expect(body.looks).toContainEqual({ | |
| 89 | look: `${kind} ${PREVIEW_HELD_PAGE}`, | |
| 90 | screens: "2.5", | |
| 91 | sideways: false, | |
| 92 | }); | |
| 93 | } | |
| 94 | }); |