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

Every forge preview takes a phone look of the sample repository's Settings scrolled to "Move to another owner", with its measured width #10272

Merged opened this

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

Merged

scout/forge:scout/10194-every-forge-preview-takes-a-phone-look-o → main 3 commits fork

  • Merged. scout/10194-every-forge-preview-takes-a-phone-look-o was merged into main as 71865b0.

Review

Sign in to review this change.
7 files changed +66 −5
src/core/looks.ts +1 −1
@@ -70,7 +70,7 @@ export const DEFAULT_PAGE = "/";
7070 * column is truth.
7171 */
7272 const pageSlug = (page: string): string =>
73 page === DEFAULT_PAGE ? "" : page.slice(1).replace(/\//g, "-");
73 page === DEFAULT_PAGE ? "" : page.slice(1).replace(/[/#]/g, "-");
7474
7575 /**
7676 * The fragment one picture is named by, on disk and in its URL: the page
src/spawn/preview-looks.ts +10
@@ -48,6 +48,15 @@ export const PREVIEW_MOVED = "moved";
4848 export const PREVIEW_MOVED_OWNER = "preview-ada";
4949 export const PREVIEW_MOVED_PAGE = `/${PREVIEW_MOVED_OWNER}/${PREVIEW_MOVED}`;
5050
51 /**
52 * The sample's Settings scrolled to "Move to another owner" by the fragment
53 * (#10194): the owner field, button and refusal sentences must fit a 390 px
54 * phone. The seed refuses to finish unless the page answers 200 and holds
55 * `id="move"`. The path is the page's own; no route is added.
56 */
57 export const PREVIEW_MOVE_PAGE = `${PREVIEW_REPO_HOME}/settings`;
58 export const PREVIEW_MOVE_LOOK = `${PREVIEW_MOVE_PAGE}#move`;
59
5160 /** The Iterations page of the seeded fork family (#9182). */
5261 export const PREVIEW_ITERATIONS_PAGE = `${PREVIEW_REPO_HOME}/iterations`;
5362 /**
@@ -142,6 +151,7 @@ export const PREVIEW_OWNER_LOOKS: readonly OwnerLook[] = [
142151 PREVIEW_REPO_HOME,
143152 PREVIEW_APP_PAGE,
144153 PREVIEW_MOVED_PAGE,
154 PREVIEW_MOVE_LOOK,
145155 PREVIEW_PULL_PAGE,
146156 PREVIEW_PULL_FILES,
147157 PREVIEW_ITERATIONS_PAGE,
src/spawn/preview-seed.ts +32
@@ -62,6 +62,7 @@ import {
6262 PREVIEW_MOVED,
6363 PREVIEW_MOVED_OWNER,
6464 PREVIEW_MOVED_PAGE,
65 PREVIEW_MOVE_PAGE,
6566 PREVIEW_REPO,
6667 PREVIEW_SETTINGS_PAGES,
6768 } from "./preview-looks.ts";
@@ -112,6 +113,33 @@ async function seedMoved(app: App, admin: User): Promise<Result<void, string>> {
112113 return ok();
113114 }
114115
116 /**
117 * The sample's Settings, asked for as the admin through `ask`, must answer
118 * 200 and hold `id="move"`: the section its phone look scrolls to (#10194).
119 * `ask` is the forge's own handler; a test hands in one that answers otherwise.
120 */
121 export async function checkMoveSection(
122 ask: (path: string) => Promise<{ readonly status: number; text(): Promise<string> }>,
123 ): Promise<Result<void, string>> {
124 const res = await ask(PREVIEW_MOVE_PAGE);
125 if (res.status !== 200) return err(`${PREVIEW_MOVE_PAGE} answers ${String(res.status)}, not 200`);
126 if (!(await res.text()).includes('id="move"')) return err(`${PREVIEW_MOVE_PAGE} holds no id="move"`);
127 return ok();
128 }
129
130 async function seedMoveSection(app: App, admin: User): Promise<Result<void, string>> {
131 const http = forgeHttp(app);
132 const session = createSession(app.db, admin.id);
133 try {
134 return await checkMoveSection((path) =>
135 http.handle(new Request(`http://localhost${path}`, { headers: { cookie: `forge_session=${session.token}` } })),
136 );
137 } finally {
138 destroySession(app.db, session.token);
139 http.close();
140 }
141 }
142
115143 /** The seeded wave's version and headline, which the owner's /waves look shows. */
116144 export const PREVIEW_WAVE = "0.1.0";
117145 export const PREVIEW_WAVE_HEADLINE = "The sample says hello";
@@ -170,6 +198,8 @@ export async function seedPreview(
170198 if (getUserSetting(app.db, admin.id, SEEDED) !== undefined) {
171199 const settings = await seedSettingsPages(app, admin);
172200 if (!settings.ok) return settings;
201 const moveSection = await seedMoveSection(app, admin);
202 if (!moveSection.ok) return moveSection;
173203 const files = await seedConfigFiles(app, admin);
174204 return files.ok ? ok({ seeded: false }) : files;
175205 }
@@ -178,6 +208,8 @@ export async function seedPreview(
178208 setUserSetting(app.db, admin.id, SEEDED, new Date().toISOString());
179209 const settings = await seedSettingsPages(app, admin);
180210 if (!settings.ok) return settings;
211 const moveSection = await seedMoveSection(app, admin);
212 if (!moveSection.ok) return moveSection;
181213 const files = await seedConfigFiles(app, admin);
182214 return files.ok ? ok({ seeded: true }) : files;
183215 }
src/web/pages/settings.ts +1 −1
@@ -416,7 +416,7 @@ export async function settingsPage(
416416 </div>
417417 </form>
418418 </section>`;
419 const move = html`<section class="settings-block">
419 const move = html`<section class="settings-block" id="move">
420420 <h2>Move to another owner</h2>
421421 <form method="get" action="/-/confirm">
422422 <input type="hidden" name="to" value="${base}/settings/transfer" />
src/web/style/components.ts +2 −2
@@ -127,8 +127,8 @@ select.input {
127127 .field > label {
128128 display: block; font-size: 13px; font-weight: 550; margin: 0 0 6px; color: var(--fg);
129129 }
130 .field .hint { display: block; font-size: 12.5px; color: var(--muted-fg); margin-top: 6px; }
131 .field .error { display: block; font-size: 12.5px; color: var(--danger); margin-top: 6px; font-weight: 500; }
130 .field .hint { display: block; font-size: 12.5px; color: var(--muted-fg); margin-top: 6px; overflow-wrap: anywhere; }
131 .field .error { display: block; font-size: 12.5px; color: var(--danger); margin-top: 6px; font-weight: 500; overflow-wrap: anywhere; }
132132 .field[data-invalid] > label { color: var(--danger); }
133133 /* A group of checks is one field: the legend reads as a label, no frame. */
134134 fieldset.field { border: 0; padding: 0; min-width: 0; }
test/looks.test.ts +1
@@ -723,6 +723,7 @@ describe("the look", () => {
723723 const dark = recordLook(app.db, { ...base, theme: "dark", path: "looks/x-dark.png" });
724724 expect(dark.id).not.toBe(old.id);
725725 expect(dark.theme).toBe("dark");
726 expect(lookSlot("/o/r/settings#move", "phone")).toBe("o-r-settings-move-phone");
726727 expect(lookSlot("/", "phone")).toBe("phone");
727728 expect(lookSlot("/", "phone", "dark")).toBe("phone-dark");
728729 expect(lookSlot("/board", "phone", "dark")).toBe("board-phone-dark");
test/preview-moved.test.ts +19 −1
@@ -9,7 +9,7 @@ import { createSession } from "../src/core/sessions.ts";
99 import { recentMove } from "../src/core/transfer.ts";
1010 import { getRepo } from "../src/core/repos.ts";
1111 import { PREVIEW_ADMIN, PREVIEW_MOVED, PREVIEW_MOVED_PAGE, PREVIEW_OWNER_LOOKS } from "../src/spawn/preview-looks.ts";
12 import { previewAdmin, seedPreview } from "../src/spawn/preview-seed.ts";
12 import { checkMoveSection, previewAdmin, seedPreview } from "../src/spawn/preview-seed.ts";
1313
1414 let dir: string;
1515 let app: App;
@@ -42,3 +42,21 @@ test("its head is looked at on desktop and phone", () => {
4242 for (const kind of ["desktop", "phone"] as const)
4343 expect(PREVIEW_OWNER_LOOKS).toContainEqual({ page: PREVIEW_MOVED_PAGE, kind });
4444 });
45
46 test("its Settings is looked at on desktop and phone, by the fragment that lands on the move section", async () => {
47 for (const kind of ["desktop", "phone"] as const)
48 expect(PREVIEW_OWNER_LOOKS).toContainEqual({ page: "/preview-admin/sample/settings#move", kind });
49 const cookie = `forge_session=${createSession(app.db, previewAdmin(app)!.id).token}`;
50 const res = await fetch(`${servers.url}/preview-admin/sample/settings`, { headers: { cookie } });
51 expect(res.status).toBe(200);
52 expect(await res.text()).toContain('id="move"');
53 });
54
55 test("the seed refuses a Settings that is not 200 or lacks the move section", async () => {
56 const answer = (status: number, body: string) => async () => ({ status, text: async () => body });
57 expect((await checkMoveSection(answer(200, '<section id="move">'))).ok).toBe(true);
58 const missing = await checkMoveSection(answer(404, ""));
59 expect(!missing.ok && missing.error).toContain("404");
60 const bare = await checkMoveSection(answer(200, "<h2>Move to another owner</h2>"));
61 expect(!bare.ok && bare.error).toContain('id="move"');
62 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs