← Files DataARCHIVED FILE
tests/data-app-runtime-loading.test.mjs
9.12 KB · Sep 30, 2026 · 23:19 UTC
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { setImmediate } from "node:timers/promises";
import test from "node:test";
import compiler from "../assets/data-app-runtime/compiler.cjs";
import { loadHostedSnapshot } from "../templates/data-app/base/src/hosted-query-bootstrap.js";
import { createQueryDataStore } from "../templates/data-app/base/src/query-data-store.js";
const source = readFileSync(new URL("../templates/data-app/base/src/DataAppRuntime.jsx", import.meta.url), "utf8");
const code = compiler.transform(source, { filePath: "DataAppRuntime.jsx" });
const snapshot = { id: "streaming-loader", title: "Complete reviewed data", surface: "dashboard",
queries: { q: { rows: [{ value: "café 日本語 🧪" }, { value: 42 }] } } };
// Exercise the actual runtime module with a small hook scheduler. Network bodies
// are real Fetch streams; state changes stay observable after disposal as well.
function runtimeFixture({ snapshotStatus = 200, presentationStatus = 200, respectAbort = true,
failedFetch, hosted = true } = {}) {
const states = new Map(), effects = new Map(), requests = [], updates = [], contentSnapshots = [];
let owner, cursor, pending = [], stream, completePresentation;
const jsx = (type, props) => ({ type, props });
const react = {
createElement: (type, props, ...children) => jsx(type, { ...props, children }),
useState(initial) {
const slots = states.get(owner) ?? [];
states.set(owner, slots);
const index = cursor++;
if (!(index in slots)) slots[index] = typeof initial === "function" ? initial() : initial;
return [slots[index], (value) => {
updates.push(value);
slots[index] = typeof value === "function" ? value(slots[index]) : value;
}];
},
useEffect(effect, dependencies) {
const slots = effects.get(owner) ?? [];
effects.set(owner, slots);
const index = cursor++;
if (!slots[index] || dependencies.some((value, position) => value !== slots[index].dependencies[position])) {
pending.push(() => {
slots[index]?.cleanup?.();
slots[index] = { dependencies, cleanup: effect() };
});
}
},
};
async function fetch(endpoint, options) {
requests.push({ endpoint, signal: options.signal });
if (failedFetch === endpoint) throw new Error("Synthetic network failure");
if (endpoint === "/api/presentation") {
return new Promise((resolve) => { completePresentation = (record) => resolve(new Response(
JSON.stringify(record), { status: presentationStatus },
)); });
}
assert.equal(endpoint, "/api/snapshot");
const body = new ReadableStream({
start(controller) {
stream = controller;
if (respectAbort) options.signal.addEventListener("abort", () => controller.error(options.signal.reason), { once: true });
},
});
const response = new Response(body, { status: snapshotStatus });
response.json = () => assert.fail("Snapshots must be read incrementally, even when small");
response.text = () => assert.fail("Snapshots must not buffer one document string");
return response;
}
const module = { exports: {} };
const modules = { react, "react/jsx-runtime": { jsx, jsxs: jsx },
"./DataAppShell.jsx": { DataAppShell: "shell" },
"./components/DataAppLoadingContent.jsx": { DataAppLoadingContent: "loading-content" },
"./hosted-query-bootstrap.js": { loadHostedSnapshot: (value, options) => loadHostedSnapshot(value, { ...options, request: fetch }) },
"./query-data-store.js": { createQueryDataStore } };
new Function("module", "exports", "require", "fetch", "AbortController", code)(
module, module.exports, (name) => {
assert.ok(Object.hasOwn(modules, name), `Unexpected runtime dependency ${name}`);
return modules[name];
}, fetch, AbortController,
);
const properties = { hosted, reviewedSnapshot: snapshot, createContent(value) {
contentSnapshots.push(value);
return { DashboardContent: () => jsx("article", { children: value.queries.q.rows.length }),
ReportContent: () => jsx("article", { children: "report" }) };
} };
function renderComponent(component, props) {
owner = component; cursor = 0;
return resolve(component(props));
}
function resolve(value) {
if (typeof value?.type === "function") return renderComponent(value.type, value.props);
return value;
}
function render() {
const view = renderComponent(module.exports.DataAppRuntime, properties);
const work = pending; pending = [];
work.forEach((effect) => effect());
return view;
}
return { requests, updates, contentSnapshots, render,
write(value) { stream.enqueue(new TextEncoder().encode(value)); },
writeBytes(bytes) { stream.enqueue(bytes); },
finish() { stream.close(); },
present(record = { presentation: { title: "Saved owner title" }, revision: 7, canEdit: true }) { completePresentation(record); },
async settled() { await setImmediate(); return render(); },
dispose() { for (const slots of effects.values()) for (const effect of slots) effect?.cleanup?.(); },
};
}
test("hosted content waits for complete streaming data and presentation, retaining all reviewed rows", async () => {
const fixture = runtimeFixture();
assert.equal(fixture.render().props["aria-busy"], "true");
const bytes = new TextEncoder().encode(JSON.stringify(snapshot));
for (const byte of bytes) fixture.writeBytes(new Uint8Array([byte]));
assert.equal((await fixture.settled()).type, "main", "Even valid complete JSON waits for the response to end");
fixture.finish();
assert.equal((await fixture.settled()).type, "main", "Presentation must finish before authored modules are created");
assert.deepEqual(fixture.contentSnapshots, []);
fixture.present();
const view = await fixture.settled();
assert.equal(view.type, "shell");
assert.deepEqual(view.props.snapshot, snapshot);
assert.deepEqual(view.props.initialPresentation, { title: "Saved owner title" });
assert.equal(view.props.initialRevision, 7);
assert.equal(view.props.canEdit, true);
assert.deepEqual(fixture.contentSnapshots, [snapshot]);
fixture.render();
assert.equal(fixture.contentSnapshots.length, 1, "Authored component identities survive subsequent renders");
fixture.dispose();
});
test("streamed hosted presentation keeps explicit owner editing semantics", async () => {
for (const canEdit of [false, undefined, "true", 1, true]) {
const fixture = runtimeFixture();
fixture.render(); fixture.write(JSON.stringify(snapshot)); fixture.finish();
fixture.present({ presentation: {}, revision: 0, canEdit });
const view = await fixture.settled();
assert.equal(view.props.canEdit, canEdit === true);
fixture.dispose();
}
});
test("invalid and truncated hosted data never instantiate authored modules", async () => {
for (const data of ['{"queries":', '{"queries":{},}', JSON.stringify(snapshot) + "x"]) {
const fixture = runtimeFixture();
fixture.render(); fixture.present(); fixture.write(data); fixture.finish();
const view = await fixture.settled();
assert.equal(view.type, "main");
assert.notEqual(view.props.children, "Loading Data app…");
assert.deepEqual(fixture.contentSnapshots, []);
assert.ok(fixture.requests.every(({ signal }) => signal.aborted), "A failed read cancels both outstanding requests");
fixture.dispose();
}
});
test("HTTP and network failures preserve the hosted loading error and cancel sibling reads", async () => {
for (const [options, message] of [
[{ snapshotStatus: 503 }, "Data app snapshot is unavailable."],
[{ presentationStatus: 403 }, "Data app presentation is unavailable."],
[{ failedFetch: "/api/snapshot" }, "Synthetic network failure"],
[{ failedFetch: "/api/presentation" }, "Synthetic network failure"],
]) {
const fixture = runtimeFixture(options);
fixture.render();
if (options.failedFetch !== "/api/presentation") fixture.present();
const view = await fixture.settled();
assert.equal(view.props.children, message);
assert.deepEqual(fixture.contentSnapshots, []);
assert.ok(fixture.requests.every(({ signal }) => signal.aborted));
fixture.dispose();
}
});
test("disposing hosted runtime aborts streams and ignores late completions from cancelled effects", async () => {
for (const respectAbort of [true, false]) {
const fixture = runtimeFixture({ respectAbort });
fixture.render(); fixture.present(); fixture.write('{"queries":');
await fixture.settled();
fixture.dispose();
assert.ok(fixture.requests.every(({ signal }) => signal.aborted));
if (!respectAbort) { fixture.write('{}}'); fixture.finish(); }
await fixture.settled();
assert.deepEqual(fixture.updates, [], "Disposed loads cannot replace state or display an abort error");
assert.deepEqual(fixture.contentSnapshots, []);
}
});
test("local runtime mounts the supplied complete snapshot without hosted reads", () => {
const fixture = runtimeFixture({ hosted: false });
const view = fixture.render();
assert.equal(view.type, "shell");
assert.equal(view.props.snapshot, snapshot);
assert.equal(view.props.canEdit, true);
assert.deepEqual(fixture.requests, []);
});
SHA-256: 37bcc1e22759516d0054687b0b09cc4bcbc0fa4568c5b1490fe0bd31199fb4ad