1import { render } from "npm:preact-render-to-string@6.7.0";2import type { PwaController, PwaState } from "../runtime/pwa.ts";3import type { StorageForkState } from "../runtime/storage-fork.ts";4import { PwaActions, pwaFailureMessage } from "./PwaActions.tsx";5import type { StorageForkSurface } from "./use-storage-fork.ts";67function controller(state: PwaState): PwaController {8 return {9 getState: () => state,10 subscribe(subscriber) {11 subscriber(state);12 return () => undefined;13 },14 requestInstall: () => Promise.resolve(state.install),15 checkForUpdate: () => Promise.resolve(false),16 applyUpdate: () => true,17 initialize: () => undefined,18 };19}2021function fork(state: StorageForkState): StorageForkSurface {22 return {23 getState: () => state,24 subscribe(subscriber) {25 subscriber(state);26 return () => undefined;27 },28 };29}3031function renderState(state: PwaState, forkState?: StorageForkState): string {32 return render(33 <PwaActions34 controller={controller(state)}35 fork={forkState ? fork(forkState) : undefined}36 />,37 );38}3940Deno.test("PwaActions renders a Chromium action only while the prompt is available", () => {41 const available = renderState({ worker: "ready", install: "available", update: "idle" });42 const dismissed = renderState({ worker: "ready", install: "dismissed", update: "idle" });43 if (!available.includes("Install app")) throw new Error("install action was omitted");44 if (dismissed.includes("Install app")) throw new Error("install action outlived its prompt");45 if (!dismissed.includes("Installation dismissed")) {46 throw new Error("dismissed outcome was hidden");47 }48});4950Deno.test("PwaActions renders complete iOS Add to Home Screen guidance", () => {51 const html = renderState({ worker: "ready", install: "manual-ios", update: "idle" });52 for (const step of ["Open the Share menu.", "Choose Add to Home Screen.", "Confirm Add."]) {53 if (!html.includes(step)) throw new Error(`iOS guidance omitted: ${step}`);54 }55 if (/unavailable in (?:the )?EU/i.test(html)) throw new Error("obsolete EU guidance returned");56});5758Deno.test("PwaActions leads iOS guidance with the fork warning while data is at risk", () => {59 const html = renderState(60 { worker: "ready", install: "manual-ios", update: "idle" },61 { state: "browser-data-at-risk" },62 );63 const warning = html.indexOf("stays in Safari and is not carried over");64 const steps = html.indexOf("Open the Share menu.");65 if (warning === -1) throw new Error("the iOS fork warning was omitted");66 if (steps === -1 || warning > steps) {67 throw new Error("the fork warning does not precede the install steps");68 }69});7071Deno.test("PwaActions omits the fork warning without local-only data", () => {72 const html = renderState(73 { worker: "ready", install: "manual-ios", update: "idle" },74 { state: "idle" },75 );76 if (html.includes("pwa-fork-warning")) throw new Error("the fork warning rendered without risk");77});7879Deno.test("PwaActions never warns about forking outside the iOS branch", () => {80 for (const install of ["available", "manual-browser"] as const) {81 const html = renderState(82 { worker: "ready", install, update: "idle" },83 { state: "browser-data-at-risk" },84 );85 if (html.includes("pwa-fork-warning")) {86 throw new Error(`the fork warning rendered for ${install}, which does not fork storage`);87 }88 }89});9091Deno.test("PwaActions renders truthful generic manual-install guidance", () => {92 const html = renderState({ worker: "ready", install: "manual-browser", update: "idle" });93 if (!html.includes("No in-page install button is available right now")) {94 throw new Error("missing prompt limitation");95 }96 if (!html.includes("if offered")) throw new Error("manual guidance invented availability");97});9899Deno.test("PwaActions renders every active update phase", () => {100 const checking = renderState({ worker: "ready", install: "installed", update: "checking" });101 const installing = renderState({ worker: "ready", install: "installed", update: "installing" });102 const ready = renderState({ worker: "ready", install: "installed", update: "ready" });103 const applying = renderState({ worker: "ready", install: "installed", update: "applying" });104 if (!checking.includes("Checking for updates")) throw new Error("checking state was omitted");105 if (!installing.includes("Installing update")) throw new Error("installing state was omitted");106 if (!ready.includes("Update app")) throw new Error("waiting-worker action was omitted");107 if (!applying.includes("Applying update")) throw new Error("applying state was omitted");108});109110Deno.test("PwaActions distinguishes unsupported installation", () => {111 const html = renderState({ worker: "unsupported", install: "unsupported", update: "idle" });112 if (!html.includes("not supported in this browser or browsing context")) {113 throw new Error("unsupported environment was hidden");114 }115});116117Deno.test("actionable PWA failure messages are fixed, useful, and non-secret", () => {118 for (119 const code of [120 "registration",121 "installation",122 "install-prompt",123 "update-check",124 "precache",125 "runtime-cache",126 ] as const127 ) {128 const message = pwaFailureMessage(code);129 if (!/(Reload|Reconnect|browser menu)/i.test(message)) {130 throw new Error(`${code} lacks an action`);131 }132 const html = renderState({133 worker: "failed",134 install: "unsupported",135 update: code === "update-check" ? "failed" : "idle",136 failure: { code, message: "secret-value-must-not-render" },137 });138 if (!html.includes(message) || html.includes("secret-value")) {139 throw new Error(`${code} failure rendering is not safely actionable`);140 }141 }142});