eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// Render smoke test for the GitHub app — the regression that motivated it:
2// the auto-load fired mid-render and hit `paint`'s temporal dead zone, threw
3// before the busy-reset, and wedged the app on "loading…" forever (every
4// later Refresh silently swallowed). Fold tests can't see render, so this
5// drives it twice over a stub DOM and proves the fetch path both RUNS and
6// RELEASES. node test/github-render.test.mjs
7import assert from "node:assert/strict";
8
9// A just-enough DOM: every element accepts children, classes, listeners.
10function fakeEl(tag = "div") {
11 return {
12 tagName: tag,
13 children: [],
14 textContent: "",
15 className: "",
16 hidden: false,
17 disabled: false,
18 value: "",
19 placeholder: "",
20 type: "",
21 onclick: null,
22 ondblclick: null,
23 appendChild(c) {
24 this.children.push(c);
25 return c;
26 },
27 append(...cs) {
28 this.children.push(...cs);
29 },
30 addEventListener() {},
31 querySelector() {
32 return fakeEl();
33 },
34 classList: { add() {}, remove() {}, toggle() {} },
35 focus() {},
36 scrollIntoView() {},
37 };
38}
39globalThis.document = {
40 createElement: (tag) => fakeEl(tag),
41 getElementById: () => null, // injectStyles' dedupe check
42 head: fakeEl("head"),
43 activeElement: null,
44 body: null,
45};
46globalThis.window = globalThis;
47
48const { app } = await import("../web/static/apps/github.js");
49
50let failures = 0;
51async function test(name, fn) {
52 try {
53 await fn();
54 console.log(`PASS ${name}`);
55 } catch (e) {
56 failures++;
57 console.log(`FAIL ${name} — ${e.message.split("\n")[0]}`);
58 }
59}
60
61const tick = () => new Promise((r) => setTimeout(r, 0));
62
63function ctxWith(requestOp) {
64 const state = { repos: [], fetchedAt: 0, issues: {} };
65 return {
66 threadId: "t1",
67 host: { connected: { requestOp } },
68 getState: () => state,
69 emit: () => {},
70 onCleanup: () => {},
71 id: () => "id",
72 };
73}
74
75await test("render survives the auto-load and RELEASES busy for the next fetch", async () => {
76 let calls = 0;
77 const ctx = ctxWith(async () => {
78 calls++;
79 return { ok: false, error: "stub" };
80 });
81 // First render auto-fetches (fetchedAt 0). A throw anywhere in that path —
82 // the shipped TDZ bug — would reject unhandled and wedge the module `busy`.
83 app.render(undefined, fakeEl(), ctx);
84 await tick();
85 await tick();
86 assert.equal(calls, 1, "auto-load ran");
87 // Second render must be able to fetch again: a wedged busy flag (the bug's
88 // signature) would swallow this silently and calls would stay at 1.
89 app.render(undefined, fakeEl(), ctx);
90 await tick();
91 await tick();
92 assert.equal(calls, 2, "busy was released — the app can fetch again");
93});
94
95await test("a requestOp that THROWS still lands in the error path and releases", async () => {
96 let calls = 0;
97 const ctx = ctxWith(async () => {
98 calls++;
99 throw new Error("boom");
100 });
101 app.render(undefined, fakeEl(), ctx);
102 await tick();
103 await tick();
104 app.render(undefined, fakeEl(), ctx);
105 await tick();
106 await tick();
107 assert.equal(calls, 2, "a thrown fetch must not wedge the app");
108});
109
110console.log(failures === 0 ? "\nALL PASSED" : `\n${failures} FAILED`);
111process.exit(failures === 0 ? 0 : 1);
112