clone

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

fix: prove cloud reconnect settlement

Dami committed f7e59b6
commit
f7e59b6c3c2a167b518f88d0d6eb89d342ae9175
parent
fa038de1d01839b21d1573f2c1c3f9ff70476428
author
Dami <dami.etoile@gmail.com>
6 files changed, +60 -20
Mapps/reference/astro.config.ts9++---
Mapps/reference/src/_lofi/checklist-store.ts1+
Mapps/reference/src/_lofi/config.ts8+++-
Mapps/reference/src/_lofi/probe.ts4++
Mapps/reference/src/env.d.ts3++
Mtools/golden_path.ts55+++++++++++++++++++++++-------
M apps/reference/astro.config.ts +4 -5
M apps/reference/src/_lofi/checklist-store.ts +1 -0
M apps/reference/src/_lofi/config.ts +6 -2
M apps/reference/src/_lofi/probe.ts +4 -0
M apps/reference/src/env.d.ts +3 -0
M tools/golden_path.ts +42 -13
@@ -63,6 +63,7 @@ const updatedBody = "Golden path retained item updated";
6363 const disposableBody = "Golden path disposable item";6464 const offlineDevelopmentBody = "Golden path development offline item";6565 const offlineProductionBody = "Golden path production offline item";66+const offlineProductionSyncedBody = "Golden path production offline item synced";6667 6768 function assert(condition: unknown, message: string): asserts condition {6869 if (!condition) throw new Error(message);
@@ -85,14 +86,11 @@ async function waitForItem(
8586 const island = page.locator("[data-island]").filter({ hasText: label });8687 const item = island.getByRole("listitem").filter({ hasText: body });8788 if (index === 0) {88- await Promise.race([89- item.waitFor({ state: "visible" }),90- island.getByRole("status").filter({ hasText: "Write failed:" }).waitFor({91- state: "visible",92- }).then(async () => {93- throw new Error((await island.getByRole("status").textContent()) ?? "Write failed");94- }),95- ]);89+ const failure = island.getByRole("status").filter({ hasText: "Write failed:" });90+ await item.or(failure).waitFor({ state: "visible" });91+ if (await failure.isVisible()) {92+ throw new Error((await failure.textContent()) ?? "Write failed");93+ }9694 } else {9795 await item.waitFor({ state: "visible" });9896 }
@@ -168,16 +166,35 @@ async function assertRuntimeCardinality(
168166 });169167 }170168 169+async function setReferenceConnection(page: Page, connected: boolean): Promise<void> {170+ await page.evaluate(async (shouldConnect) => {171+ const probe = (globalThis as typeof globalThis & {172+ __LOFI_REFERENCE__?: {173+ disconnect(): Promise<void>;174+ reconnect(): Promise<void>;175+ };176+ }).__LOFI_REFERENCE__;177+ if (!probe) throw new Error("development reference probe is unavailable");178+ if (shouldConnect) await probe.reconnect();179+ else await probe.disconnect();180+ }, connected);181+}182+171183 async function observeReconnectSettlement(172184 page: Page,173185 timeoutMs: number,174186 required: boolean,175187 ): Promise<JourneyAssertion> {176188 try {177- await page.getByRole("status").filter({ hasText: /last write global/ }).first().waitFor({189+ const global = page.getByRole("status").filter({ hasText: /last write global/ }).first();190+ const failure = page.getByRole("status").filter({ hasText: "Write failed:" }).first();191+ await global.or(failure).waitFor({178192 state: "visible",179193 timeout: Math.min(timeoutMs, 10_000),180194 });195+ if (await failure.isVisible()) {196+ throw new Error((await failure.textContent()) ?? "Write failed");197+ }181198 return {182199 name: "development reconnect settlement",183200 status: "passed",
@@ -244,13 +261,14 @@ async function completeItem(page: Page, body: string): Promise<void> {
244261 const name = new RegExp(`^(?:Complete ${escaped(body)}|Mark ${escaped(body)} complete)$`);245262 const checkboxes = page.getByRole("checkbox", { name });246263 try {247- await checkboxes.first().check();264+ await checkboxes.first().waitFor({ state: "visible" });248265 } catch (error) {249266 throw new Error(250267 `reference UI needs checkbox "Complete ${body}" or "Mark ${body} complete"`,251268 { cause: error },252269 );253270 }271+ await checkboxes.first().click();254272 await page.waitForFunction((accessibleName) => {255273 const matches = [...document.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')]256274 .filter((element) =>
@@ -474,11 +492,13 @@ export async function runJourney(options: GoldenPathOptions): Promise<JourneyRep
474492 detail: "Create, update, complete, and delete worked through named author-facing controls.",475493 });476494 495+ if (environmentMode === "cloud-from-root") await setReferenceConnection(page, false);477496 await context.setOffline(true);478497 await addItem(page, islandLabels[1], offlineDevelopmentBody);479498 await waitForItem(page, islandLabels, offlineDevelopmentBody);480499 await waitForLocalDurability(page);481500 await context.setOffline(false);501+ if (environmentMode === "cloud-from-root") await setReferenceConnection(page, true);482502 assertions.push(483503 await observeReconnectSettlement(page, timeoutMs, environmentMode === "cloud-from-root"),484504 );
@@ -543,8 +563,17 @@ export async function runJourney(options: GoldenPathOptions): Promise<JourneyRep
543563 await page.reload({ waitUntil: "domcontentloaded", timeout: timeoutMs });544564 await waitForItem(page, islandLabels, offlineProductionBody);545565 await context.setOffline(false);566+ if (environmentMode === "cloud-from-root") {567+ await updateItem(page, offlineProductionBody, offlineProductionSyncedBody);568+ await waitForItem(page, islandLabels, offlineProductionSyncedBody);569+ assertions.push(await observeReconnectSettlement(page, timeoutMs, true));570+ }546571 await page.reload({ waitUntil: "domcontentloaded", timeout: timeoutMs });547- await waitForItem(page, islandLabels, offlineProductionBody);572+ await waitForItem(573+ page,574+ islandLabels,575+ environmentMode === "cloud-from-root" ? offlineProductionSyncedBody : offlineProductionBody,576+ );548577 assertions.push({549578 name: "production offline cold start",550579 status: "passed",
@@ -552,11 +581,11 @@ export async function runJourney(options: GoldenPathOptions): Promise<JourneyRep
552581 });553582 554583 if (environmentMode === "cloud-from-root") {555- for (const body of [updatedBody, offlineDevelopmentBody, offlineProductionBody]) {584+ for (const body of [updatedBody, offlineDevelopmentBody, offlineProductionSyncedBody]) {556585 await deleteItem(page, body);557586 await waitForItemAbsent(page, islandLabels, body);587+ assertions.push(await observeReconnectSettlement(page, timeoutMs, true));558588 }559- assertions.push(await observeReconnectSettlement(page, timeoutMs, true));560589 assertions.push({561590 name: "cloud fixture cleanup",562591 status: "passed",