Testing and SSR
Wire-faithful parity clients, the fetch-handler-as-fetch harness, and validated hydration.
import { createParityClient } from "result-rpc/testing";
import { appRouter } from "./router";
const client = createParityClient(appRouter, {
context: testContext,
});
const result = await client.doc.byId({ id: "missing" });
expect(result).toEqual({
status: "error",
error: {
_tag: "doc/not-found",
data: { docId: "missing" },
},
});
Parity mode runs the same codecs and undeclared-error checks as the remote protocol. A separate test transport covers malformed envelopes, 5xx responses, timeouts, offline behavior, and batch failures.
And when a test should cross the real wire — protocol, serializer, HTTP statuses and all — the fetch handler is the fetch, no server process required. The examples run their full React trees this way:
const handler = createFetchHandler({ router, createContext: () => context });
const client = createBrowserClient({
contract: appContract,
transport: fetchTransport({
url: "https://example.test/rpc",
fetch: (input, init) => handler(new Request(input, init)),
}),
});
// full round-trip: devalue envelope, HTTP status, rich values intact
const events = await collect(client.doc.events({ id: "doc_1" }));
expect(events[0]).toEqual(ok({ docId: "doc_1", kind: "renamed", at: new Date("2026-01-01") }));
Note the Date inside the assertion — it crossed the wire as a Date.
Simulating offline
The connectivity source listens on globalThis (in a browser that is
window; in Bun and Node it is not — do not stub window). Its online
snapshot is seeded from navigator.onLine at module import and is
event-driven afterwards — flipping navigator.onLine does nothing; you
dispatch events. Listeners attach lazily when the first subscriber appears
(creating a runtime or mounting BoundaryProvider), so mount first, dispatch
second:
const runtime = createQueryRuntime({ client }); // attaches listeners
// ... render under BoundaryProvider ...
await act(async () => {
globalThis.dispatchEvent(new Event("offline")); // reads pause, banner flips
});
await act(async () => {
globalThis.dispatchEvent(new Event("online")); // held work resumes
});
Always dispatch a final online in test teardown — the source is a module
singleton, and a test that leaves it offline starves the next one.