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

A test runs the toolbar pill's script and proves it draws one freshness line under "Preview of pull #N" for a merged, closed, deploying or behind preview, and nothing for an open, current one #9141

Merged opened this

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

Merged

mason/forge:mason/9140-a-test-runs-the-toolbar-pill-s-script-an → main 1 commit fork

  • Merged. mason/9140-a-test-runs-the-toolbar-pill-s-script-an was merged into main as e5463f3.

Review

Sign in to review this change.
2 files changed +105 −5
src/apps/toolbar.ts +13 −4
@@ -15,6 +15,14 @@ import { staticResponse } from "../web/asset.ts";
1515 import { sha256 } from "../kernel/hash.ts";
1616 import { NOTE_EMPTY, NOTE_FAILED } from "./note_words.ts";
1717
18 /** What the panel says about a preview's freshness (#9111): the pill's words, in one table. */
19 export const FRESHNESS_WORDS = {
20 merged: "Merged: this preview shows the pull as it landed",
21 closed: "Closed without landing",
22 deploying: "A newer push is deploying",
23 behind: "Shows an older push than the pull's newest",
24 } as const;
25
1826 /** The client, as plain source: no `eval`, no inline handlers. */
1927 export const toolbarJs = `(function () {
2028 if (window.__forgeToolbar) return;
@@ -89,10 +97,11 @@ export const toolbarJs = `(function () {
8997 } else if (d.pull) line("Preview of pull #" + d.pull);
9098 if (d.title) line(d.title);
9199 // Whether this is the pull as it stands now; an open, current preview says nothing.
92 var freshness = d.state === "merged" ? "Merged: this preview shows the pull as it landed"
93 : d.state === "closed" ? "Closed without landing"
94 : d.deploying ? "A newer push is deploying"
95 : d.behind ? "Shows an older push than the pull's newest" : "";
100 var W = ${JSON.stringify(FRESHNESS_WORDS)};
101 var freshness = d.state === "merged" ? W.merged
102 : d.state === "closed" ? W.closed
103 : d.deploying ? W.deploying
104 : d.behind ? W.behind : "";
96105 if (freshness) line(freshness);
97106 var link = document.createElement("a");
98107 link.textContent = "Home";
test/apps-pill-freshness.test.ts +92 −1
@@ -9,7 +9,7 @@ import { join } from "node:path";
99 import { createApp, type App } from "../src/app.ts";
1010 import { newApp } from "../src/actions/apps.ts";
1111 import { pillWhere, proxyApp, signGrant, type AppSocket, type AppTarget } from "../src/apps/proxy.ts";
12 import { toolbarJs, toolbarTag } from "../src/apps/toolbar.ts";
12 import { FRESHNESS_WORDS, toolbarJs, toolbarTag } from "../src/apps/toolbar.ts";
1313 import { AppsRuntime, type EnvStatus } from "../src/apps/runtime.ts";
1414 import { getAppByRepo } from "../src/core/apps.ts";
1515 import { createIssue } from "../src/core/issues.ts";
@@ -203,3 +203,94 @@ describe("the pill's freshness, through the proxy", () => {
203203 live = HEAD;
204204 });
205205 });
206
207 describe("the pill's script draws the freshness line", () => {
208 /** A stand-in element: it keeps what the script sets and who it holds, and nothing more. */
209 class El {
210 children: El[] = [];
211 attrs: Record<string, string> = {};
212 style: Record<string, unknown> = {};
213 dataset: Record<string, string> = {};
214 textContent = "";
215 [k: string]: unknown;
216 constructor(public tag: string) {}
217 setAttribute(k: string, v: string) { this.attrs[k] = v; }
218 removeAttribute() {}
219 appendChild(c: El) { this.children.push(c); return c; }
220 append(...c: El[]) { this.children.push(...c); }
221 prepend(c: El) { this.children.unshift(c); }
222 addEventListener() {}
223 removeEventListener() {}
224 attachShadow() { return new El("#shadow"); }
225 focus() {}
226 getBoundingClientRect() { return { left: 0, top: 0, width: 0, height: 0 }; }
227 }
228
229 /** Run the script on a page whose tag carries `data`; return the panel's lines, top to bottom. */
230 function panelLines(data: Record<string, string>): string[] {
231 const made: El[] = [];
232 const me = new El("script");
233 me.dataset = { forge: "http://forge.test", ...data };
234 const doc = {
235 currentScript: me,
236 readyState: "complete",
237 documentElement: new El("html"),
238 body: new El("body"),
239 head: new El("head"),
240 createElement: (t: string) => { const e = new El(t); made.push(e); return e; },
241 addEventListener() {},
242 removeEventListener() {},
243 };
244 const win: Record<string, unknown> = {
245 innerWidth: 1000, innerHeight: 800, scrollX: 0, scrollY: 0,
246 localStorage: { getItem: () => null, setItem() {} },
247 addEventListener() {}, removeEventListener() {},
248 };
249 const loc = { href: "http://site.test/", origin: "http://site.test", pathname: "/", search: "" };
250 const never = () => new Promise<never>(() => {});
251 new Function("window", "document", "location", "fetch", "getComputedStyle", "requestAnimationFrame", "setTimeout", "setInterval",
252 toolbarJs)(win, doc, loc, never, () => ({}), () => 0, () => 0, () => 0);
253 const panel = made.find((e) => e.attrs["role"] === "region");
254 if (!panel) throw new Error("no panel");
255 return panel.children.map((c) => c.textContent).filter((t) => t !== "");
256 }
257
258 const base = { app: "site", repo: "alice/site", env: "pull-7", pull: "7", title: "Add a thing" };
259 const words = Object.values(FRESHNESS_WORDS);
260 /** The lines after the title, if the panel has one freshness line among the pull's own. */
261 const after = (lines: string[]) => lines.slice(lines.indexOf("Add a thing") + 1).filter((l) => words.includes(l as never));
262
263 test("merged, closed, deploying and behind each draw one line, right after the pull and its title", () => {
264 const cases: [Record<string, string>, string][] = [
265 [{ state: "merged" }, FRESHNESS_WORDS.merged],
266 [{ state: "closed" }, FRESHNESS_WORDS.closed],
267 [{ state: "open", deploying: "1" }, FRESHNESS_WORDS.deploying],
268 [{ state: "open", behind: "1" }, FRESHNESS_WORDS.behind],
269 ];
270 for (const [extra, said] of cases) {
271 const lines = panelLines({ ...base, ...extra });
272 expect(lines.slice(0, 4)).toEqual(["site", "Preview of pull #7", "Add a thing", said]);
273 expect(lines.filter((l) => words.includes(l as never))).toEqual([said]);
274 }
275 });
276
277 test("the line follows the pull when there is no title", () => {
278 const lines = panelLines({ ...base, title: "", state: "merged" });
279 expect(lines.slice(0, 3)).toEqual(["site", "Preview of pull #7", FRESHNESS_WORDS.merged]);
280 });
281
282 test("merged and closed win over deploying and behind; deploying wins over behind", () => {
283 const both = { deploying: "1", behind: "1" };
284 expect(after(panelLines({ ...base, state: "merged", ...both }))).toEqual([FRESHNESS_WORDS.merged]);
285 expect(after(panelLines({ ...base, state: "closed", ...both }))).toEqual([FRESHNESS_WORDS.closed]);
286 expect(after(panelLines({ ...base, state: "open", ...both }))).toEqual([FRESHNESS_WORDS.deploying]);
287 });
288
289 test("an open, current preview draws none; so does production; so does a viewer told nothing", () => {
290 expect(panelLines({ ...base, state: "open" }).filter((l) => words.includes(l as never))).toEqual([]);
291 expect(panelLines({ ...base }).filter((l) => words.includes(l as never))).toEqual([]);
292 const prod = panelLines({ app: "site", repo: "alice/site", env: "production" });
293 expect(prod.slice(0, 2)).toEqual(["site", "Production"]);
294 expect(prod.filter((l) => words.includes(l as never))).toEqual([]);
295 });
296 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs