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

The app page's adapter runs list has no sideways scroll at 390px, with a look on the pull #10179

Merged opened this

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

Merged

scout/forge:scout/10158-the-app-page-s-adapter-runs-list-has-no → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/10158-the-app-page-s-adapter-runs-list-has-no was merged into main as eba2f6b.

Review

Sign in to review this change.
2 files changed +108 −2
src/spawn/preview-seed.ts +25 −2
@@ -34,7 +34,7 @@ import { iterationsOf } from "../core/iterations.ts";
3434 import { turnValveFor } from "../actions/valves.ts";
3535 import { insertBet } from "../core/bets.ts";
3636 import { addDependency } from "../core/dependencies.ts";
37 import { claimRun, createRun, finishRun } from "../core/runs.ts";
37 import { claimRun, createPullRun, createRun, finishRun } from "../core/runs.ts";
3838 import { setMember } from "../core/roles.ts";
3939 import { noteLimit } from "../core/standing.ts";
4040 import { addSubscription, listSubscriptions } from "../core/subscriptions.ts";
@@ -574,11 +574,15 @@ async function seedApp(app: App, admin: User): Promise<Result<void, string>> {
574574 if (!repo.ok) return err(`the sample app's repository: ${String(repo.error)}`);
575575 const first = await commitToBranch(app, admin, repo.value, {
576576 message: "Start",
577 files: [{ path: "README.md", content: "# hello\n\nA preview's sample app.\n" }],
577 files: [
578 { path: "README.md", content: "# hello\n\nA preview's sample app.\n" },
579 { path: "forge.json", content: JSON.stringify({ app: { run: "bun run src/server.ts" }, adapter: { schedule: { every: 900 } } }) },
580 ],
578581 });
579582 if (!first.ok) return err(`the sample app's commit: ${JSON.stringify(first.error)}`);
580583 const record = createAppRecord(app.db, { repoId: repo.value.id, slug: PREVIEW_APP, access: "public" });
581584 if (!record.ok) return err(`the sample app's record: ${record.error}`);
585 seedPullRuns(app, record.value.id);
582586 const url = new URL(`https://preview.invalid${PREVIEW_APP_PAGE}`);
583587 const answer = await studioApp(
584588 { app, request: new Request(url.href), url, params: {}, auth: { user: admin }, theme: undefined } as Ctx,
@@ -587,3 +591,22 @@ async function seedApp(app: App, admin: User): Promise<Result<void, string>> {
587591 );
588592 return answer.status === 200 ? ok() : err(`${PREVIEW_APP_PAGE} would answer ${String(answer.status)}, not 200`);
589593 }
594
595 /** Ten recorded pull runs for the app page's Adapter section (#10158): one failed with a reason of the longest allowed, an unbroken word in it. */
596 export const PREVIEW_LONG_REASON = `upstream refused the pull: ${"x".repeat(60)} ${"https://api.example.invalid/v2/orders?cursor=".repeat(3)}`.slice(0, 200);
597 function seedPullRuns(app: App, appId: string): void {
598 for (let i = 0; i < 10; i++) {
599 const failed = i === 4;
600 createPullRun(app.db, {
601 appId,
602 state: failed ? "failed" : "ok",
603 written: failed ? 0 : 12 + i,
604 skipped: failed ? 0 : 3,
605 failed: 0,
606 cursor: failed ? "" : `c${String(i)}`,
607 durationMs: 40 + i,
608 reason: failed ? PREVIEW_LONG_REASON : "",
609 repeat: false,
610 });
611 }
612 }
test/adapter-runs-fit.test.ts added +83
@@ -0,0 +1,83 @@
1 /**
2 * The app page's Adapter section with ten recorded runs, one failed with a long
3 * reason, fits a 390 px phone (#10158). Where a Chromium starts, the page is
4 * measured at 390x844; the seeded preview app carries the same runs for its look.
5 */
6 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
7 import { mkdtemp, rm } from "node:fs/promises";
8 import { tmpdir } from "node:os";
9 import { join } from "node:path";
10 import { newApp } from "../src/actions/apps.ts";
11 import { createApp, type App } from "../src/app.ts";
12 import { createPullRun } from "../src/core/runs.ts";
13 import { createSession } from "../src/core/sessions.ts";
14 import { getRepo } from "../src/core/repos.ts";
15 import { getUserByName, createUser } from "../src/core/users.ts";
16 import { commitFiles } from "../src/git/write.ts";
17 import { repoDir } from "../src/git/repos.ts";
18 import { serve, type Servers } from "../src/http/server.ts";
19 import { PREVIEW_APP_PAGE, PREVIEW_OWNER_LOOKS } from "../src/spawn/preview-looks.ts";
20 import { PREVIEW_LONG_REASON } from "../src/spawn/preview-seed.ts";
21 import { findChromium } from "../src/looks/capture.ts";
22 import { chromiumRuns, evaluateLoaded } from "./helpers/chrome.ts";
23
24 const REASON = `<b>boom</b> ${"Z".repeat(190)}`;
25 let dir: string, app: App, servers: Servers, cookie: string, page: string;
26
27 beforeAll(async () => {
28 dir = await mkdtemp(join(tmpdir(), "forge-adapter-runs-fit-"));
29 app = createApp({ dataDir: dir, port: 0 });
30 servers = await serve(app);
31 const made = await createUser(app.db, { origin: "seed", trusted: true, username: "alice", email: "[email protected]", password: "a-good-passphrase", adminIfFirst: true });
32 if (!made.ok) throw new Error(made.error);
33 const u = getUserByName(app.db, "alice")!;
34 const n = await newApp(app, u, { name: "hello", visibility: "public" } as never);
35 if (!n.ok) throw new Error(String(n.error));
36 const repo = getRepo(app.db, "alice", "hello")!;
37 const appId = (app.db.query("SELECT id FROM app WHERE repo_id = ?1").get(repo.id) as { id: string }).id;
38 const c = await commitFiles(repoDir(app.reposRoot, "alice", "hello"), {
39 branch: repo.defaultBranch,
40 message: "forge.json",
41 files: [{ path: "forge.json", content: JSON.stringify({ run: "bun run src/server.ts", adapter: { schedule: { every: 900 } } }) }],
42 author: { name: "T", email: "[email protected]" },
43 });
44 if (!c.ok) throw new Error(JSON.stringify(c.error));
45 for (let i = 0; i < 12; i++) {
46 const failed = i === 5;
47 createPullRun(app.db, { appId, state: failed ? "failed" : "ok", written: 3, skipped: 1, failed: 0, cursor: failed ? "" : `c${String(i)}`, durationMs: 5, reason: failed ? REASON : "", repeat: false });
48 }
49 cookie = `forge_session=${createSession(app.db, u.id).token}`;
50 page = await (await fetch(`${servers.url}/alice/hello`, { headers: { cookie } })).text();
51 }, 60_000);
52 afterAll(async () => {
53 servers.stop();
54 app.close();
55 await rm(dir, { recursive: true, force: true });
56 });
57

This only proves the break class is on each row. If .break lost its overflow-wrap, this still passes, and the measured test skips without Chromium. Add an assertion on the stylesheet rule.

58 describe("the Adapter section's runs", () => {
59 test("ten runs are listed, the long reason escaped and in a wrapping row", () => {
60 expect(page.match(/<li class="break">/g)?.length).toBe(10);
61 expect(page).toContain("&lt;b&gt;boom&lt;/b&gt;");
62 expect(page).not.toContain("<b>boom</b>");
63 expect(page).toContain("Z".repeat(190));
64 });
65
66 test("the preview's seeded app and phone look carry the same case", () => {
67 expect(PREVIEW_LONG_REASON.length).toBe(200);
68 expect(PREVIEW_OWNER_LOOKS).toContainEqual({ page: PREVIEW_APP_PAGE, kind: "phone" });
69 });
70 });
71
72 const found = findChromium();
73 const chromium = found !== undefined && chromiumRuns(found) ? found : undefined;
74 if (chromium === undefined) console.log("SKIPPING: no chromium that starts here, so the app page is not measured");
75
76 describe.skipIf(chromium === undefined)("measured at 390x844", () => {
77 test("no sideways scroll", async () => {
78 const measure = `(() => JSON.stringify({ scrollWidth: document.documentElement.scrollWidth, viewportWidth: document.documentElement.clientWidth, runs: document.querySelectorAll(".pull-runs li").length }))()`;
79 const m = JSON.parse(await evaluateLoaded(chromium!, `${servers.url}/alice/hello`, { width: 390, height: 844 }, [{ name: "forge_session", value: cookie.split("=")[1]! }], measure));
80 expect(m.runs).toBe(10);
81 expect(m.scrollWidth).toBeLessThanOrEqual(m.viewportWidth);
82 }, 30_000);
83 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs