pi-mono/packages/chord/test/next-node/react/hooks.test.ts
Mario Zechner 5bc4145165 feat(chord): Chord v2 web and terminal surfaces (slice 11)
- Web: base bundles built from the app's entry at start (content-hashed, served
  forever), plugin scripts that register themselves and one combined file,
  WebScripts/TuiScripts showing the current set, startPage with the connection
  loop, in-place plugin reloads and a reload on a new base bundle.
- React bindings: HostProvider, useHold, useWatch, useRegistry, useRefState.
- Terminal: startTerminal, a launcher that signs in with a token, a token file or
  device pairing and always runs the app's terminal code in a sandboxed child
  process (pipes, empty environment, --permission granting nothing, no SQLite,
  a builtin allowlist and a frozen process inside); output filter, confirmed
  copy/open/attach actions; startTerminalRuntime and TuiScope inside.
- Themes: the Themes registry, ThemeChoice per principal, CSS variables on the
  page, terminal colours by role; CSS modules in plugin builds.
- App kit: startServer gains web and tui; startApp renamed startServer,
  exportAddress/importAddress renamed exportData/importData and export no longer
  stops the worker; ephemeral sessions end with their client's last link.
- Examples 56-web, 57-tui, 58-themes, 59-chat.
2026-11-02 21:31:59 +00:00

45 lines
2.1 KiB
TypeScript

// Tests of the React bindings (slice 11 §3) in a real browser (Chromium; skipped without one), on the fixture's page:
// `useHold` holds while mounted and releases on unmount, `useWatch` re-renders on change, `useRefState` follows the
// reference's state, `useRegistry` the registry (the panels).
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { BACKGROUND_CONTEXT as context } from "../../../src/context/index.ts";
import { Browser, CHROMIUM } from "../web/browser.ts";
import { freePort, panelPlugin, signIn, versionedEntry, webApp } from "../web/support.ts";
describe.skipIf(CHROMIUM === undefined)("React bindings", () => {
let browser: Browser;
beforeAll(async () => {
browser = await Browser.start();
}, 120_000);
afterAll(async () => {
await browser?.close();
});
it("hold while mounted, re-render on change, release on unmount", async () => {
const { app } = await webApp(await versionedEntry("1"), await freePort(), {
plugins: { notes: panelPlugin("Notes", "#123456") },
});
try {
await signIn(browser, app.ownerLink());
// useRegistry: the plugin's panel.
await browser.wait('document.querySelector(".panel")?.textContent === "Notes"');
// useHold + useRefState: the counter held and live.
await browser.wait('document.getElementById("count")?.dataset.state === "live"');
expect(await browser.evaluate<string>('document.getElementById("count").textContent')).toBe("0");
// useWatch: a change re-renders.
await browser.evaluate('document.getElementById("count").click()');
await browser.wait('document.getElementById("count").textContent === "1"');
// Unmounted: the reference is released.
await browser.evaluate('document.getElementById("toggle").click()');
await browser.wait('document.getElementById("count") === null && globalThis.counterState() === "ended"');
// Mounted again: held again.
await browser.evaluate('document.getElementById("toggle").click()');
await browser.wait('document.getElementById("count")?.dataset.state === "live"');
} finally {
await app.close(context);
}
}, 120_000);
});