clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/141-effect-library
lofi / package / testing / scenario / scenario.ts
scenario.ts 2.9K
1import {2 type BrowserScenarioContext,3 type BrowserScenarioOptions,4 runBrowserScenario,5} from "./browser.ts";6import { type FuzzScenarioOptions, runFuzzScenario } from "./fuzz.ts";7import type { ScenarioApp } from "./headless.ts";8import { runHeadlessScenario, type ScenarioConfig, type ScenarioContext } from "./run.ts";910/**11 * The scenario entry points. The call signature registers a headless12 * scenario; {@link ScenarioApi.fuzz} and {@link ScenarioApi.browser} register13 * the fuzz and browser flavors. Each call registers one `Deno.test`, so14 * scenarios are declared at the top level of a test module.15 */16export interface ScenarioApi {17 /**18 * Declare a headless scenario: two peers (alice and bob) on real synced19 * clients against a local sync server, driving the app's own tables.20 */21 <A extends ScenarioApp>(22 name: string,23 config: ScenarioConfig<A>,24 body: (context: ScenarioContext<A>) => Promise<void>,25 ): void;26 /**27 * Declare a fuzz scenario: a seeded, replayable operation sequence across28 * both peers, checked for convergence — including against a fresh reader.29 */30 fuzz<A extends ScenarioApp>(name: string, options: FuzzScenarioOptions<A>): void;31 /**32 * Declare a browser scenario: two Playwright clients against a served app,33 * with page-mediated edits and value-free snapshots. Skips cleanly when no34 * `baseURL` is configured or no browser is installed.35 */36 browser(37 name: string,38 options: BrowserScenarioOptions,39 body: (context: BrowserScenarioContext) => Promise<void>,40 ): void;41}4243/**44 * Declare simulation-test scenarios: named peers make concurrent and offline45 * edits through the app's own schema, and the test asserts they converge.46 *47 * ```ts48 * scenario("offline rename vs remote delete", { app, permissions }, async ({ alice, bob }) => {49 * const doc = await alice.db.documents.insert({ title: "Untitled" });50 * await converge(alice, bob);51 *52 * await alice.offline();53 * await alice.db.documents.update(doc.id, { title: "Draft" });54 * await bob.db.documents.remove(doc.id);55 * await alice.online();56 * await alice.settle();57 *58 * await alice.restart(); // the live writer keeps its doomed rename until it restarts59 * await converge(alice, bob);60 * await assertNoRow(alice, app.documents, doc.id);61 * });62 * ```63 */64export const scenario: ScenarioApi = Object.assign(65 function scenario<A extends ScenarioApp>(66 name: string,67 config: ScenarioConfig<A>,68 body: (context: ScenarioContext<A>) => Promise<void>,69 ): void {70 Deno.test(name, () => runHeadlessScenario(config, body));71 },72 {73 fuzz<A extends ScenarioApp>(name: string, options: FuzzScenarioOptions<A>): void {74 Deno.test(name, () => runFuzzScenario(options));75 },76 browser(77 name: string,78 options: BrowserScenarioOptions,79 body: (context: BrowserScenarioContext) => Promise<void>,80 ): void {81 Deno.test(name, () => runBrowserScenario(options, body));82 },83 },84);