Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
4.6 kB · 132 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133/** * The panel that stands in for the sign-in form when the API is down must not * be a sign-in form. * * The whole reason the front page used to throw itself away during an outage * was the reasoning in router.ts: a form that cannot submit is worse than no * form, so rather than show one the router showed the error screen instead — * and took the headline, the pitch and the three facts down with it, none of * which ever asked the API for anything. * * Keeping the page therefore only works while the panel keeps the other half * of that bargain. A field added here later would collect a handle and fail * with it, silently and on the front page, so this asserts the absence: no * form, no input, and an alert a screen reader is actually told about. * * A real test rather than a source read, which outage-panel.ts is written to * allow: it imports ./dom and nothing else, so the fake below is enough. Its * neighbours in the hero cannot be tested this way — chrome.ts asks the API * for its version at import time, which is right in a browser and wrong here. * * The module under test is TypeScript with extensionless imports, so this * bundles it the way profile.test.mjs does. Run with `npm test`. */import { test } from "node:test";import assert from "node:assert/strict";import { mkdtemp, rm } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { execFile } from "node:child_process";import { promisify } from "node:util";
// The same shape dom.test.mjs uses, plus the listener el() never touches and// this panel's button does.class FakeElement { constructor(tag) { this.tagName = tag.toUpperCase(); this.attributes = {}; this.children = []; this.listeners = {}; } setAttribute(name, value) { this.attributes[name] = value; } append(...nodes) { this.children.push(...nodes); } addEventListener(type, handler) { this.listeners[type] = handler; } /** Every element in the tree, this one included. */ *walk() { yield this; for (const child of this.children) if (child.walk) yield* child.walk(); }}
globalThis.document = { createElement: (tag) => new FakeElement(tag) };
const run = promisify(execFile);const web = join(new URL(".", import.meta.url).pathname, "..");
const dir = await mkdtemp(join(tmpdir(), "outage-panel-"));const bundle = join(dir, "fixture.mjs");await run( join(web, "node_modules/.bin/rolldown"), [ join(web, "scripts/outage-panel.fixture.ts"), "--format", "esm", "--platform", "node", "--file", bundle, ], { cwd: web },);
const { outagePanel } = await import(bundle);
/** The message api.ts writes when fetch itself could not reach the API. */const MESSAGE = "Cannot reach the control plane.";
test("the panel asks for nothing", () => { const panel = outagePanel(MESSAGE, () => {}); const tags = [...panel.walk()].map((node) => node.tagName);
for (const asking of ["FORM", "INPUT", "SELECT", "TEXTAREA"]) { assert.ok( !tags.includes(asking), `the outage panel grew a <${asking.toLowerCase()}> — whatever stopped ` + `/api/session will stop the submit too`, ); }});
test("the message is announced, not just printed", () => { const panel = outagePanel(MESSAGE, () => {}); // A property and not an attribute, because `role` has no hyphen and el() // splits on that — which is a real assignment in a browser, where role is // reflected. See dom.test.mjs for why that split exists at all. const alert = [...panel.walk()].find((node) => node.role === "alert");
assert.ok( alert, "nothing on the panel is an alert — a player who pressed Sign in and got " + "this panel back is told nothing", ); assert.equal(alert.textContent, MESSAGE);});
test("try again runs the retry, once per press", () => { let asked = 0; const panel = outagePanel(MESSAGE, () => { asked += 1; }); const button = [...panel.walk()].find((node) => node.tagName === "BUTTON");
assert.ok(button, "the panel has no way out of the outage"); // type="button" and not a submit: there is no form, and a bare <button> // inside one would submit it. assert.equal(button.type, "button");
button.listeners.click();
assert.equal(asked, 1); // Disabled rather than left live, so a player pressing it three times does // not queue three session reads against an API that is already not // answering. start() re-renders the panel, which is what re-enables it. assert.equal(button.disabled, true);});
test.after(() => rm(dir, { recursive: true, force: true }));