chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
FakeBrowserPermissionAdapter,
|
||||
FakeClientWorkflowAdapter,
|
||||
FakeFeatureFlagAdapter,
|
||||
FakeFileTransferAdapter,
|
||||
FakeGeneratedApiAdapter,
|
||||
FakeLargeDataUiAdapter,
|
||||
FakeMultiTabAdapter,
|
||||
FakeRealtimeAdapter,
|
||||
FakeServiceWorkerUpdateAdapter,
|
||||
FakeWorkerTaskAdapter,
|
||||
MemoryOfflineRepository,
|
||||
RecordingAnalyticsAdapter,
|
||||
createUnavailableAdapters,
|
||||
} from "../../recipes/frontend-capabilities/index.ts";
|
||||
|
||||
describe("optional capability recipes", () => {
|
||||
it("requires realtime cleanup and rejects duplicate or out-of-order events", async () => {
|
||||
const adapter = new FakeRealtimeAdapter<{ value: string }>();
|
||||
const received: string[] = [];
|
||||
const subscription = await adapter.subscribe({
|
||||
channel: "orders",
|
||||
onEvent(result) {
|
||||
received.push(result.ok ? result.value.payload.value : result.failure.code);
|
||||
},
|
||||
});
|
||||
expect(subscription.ok).toBe(true);
|
||||
adapter.emit("orders", {
|
||||
id: "event-2",
|
||||
sequence: 2,
|
||||
occurredAt: "2026-07-26T00:00:00.000Z",
|
||||
payload: { value: "new" },
|
||||
});
|
||||
adapter.emit("orders", {
|
||||
id: "event-1",
|
||||
sequence: 1,
|
||||
occurredAt: "2026-07-26T00:00:00.000Z",
|
||||
payload: { value: "old" },
|
||||
});
|
||||
expect(received).toEqual(["new", "STALE_RESULT"]);
|
||||
if (subscription.ok) subscription.value.unsubscribe();
|
||||
expect(adapter.activeSubscriptionCount).toBe(0);
|
||||
});
|
||||
|
||||
it("models offline schema migration and closed-repository fallback", async () => {
|
||||
const repository = new MemoryOfflineRepository<{ id: string; name: string }>();
|
||||
expect(await repository.open({ schemaVersion: 1 })).toEqual({
|
||||
ok: true,
|
||||
value: undefined,
|
||||
});
|
||||
await repository.put({ id: "one", name: "offline" });
|
||||
expect(await repository.migrate({ from: 1, to: 2 })).toEqual({
|
||||
ok: true,
|
||||
value: undefined,
|
||||
});
|
||||
expect(await repository.get("one")).toEqual({
|
||||
ok: true,
|
||||
value: { id: "one", name: "offline" },
|
||||
});
|
||||
repository.close();
|
||||
expect((await repository.get("one")).ok).toBe(false);
|
||||
});
|
||||
|
||||
it("covers service-worker stale update and explicit unregister", async () => {
|
||||
const adapter = new FakeServiceWorkerUpdateAdapter("v1", "v2");
|
||||
expect((await adapter.inspect()).ok).toBe(true);
|
||||
expect((await adapter.activate("stale")).ok).toBe(false);
|
||||
expect(await adapter.activate("v2")).toEqual({ ok: true, value: undefined });
|
||||
expect(await adapter.unregister()).toEqual({ ok: true, value: undefined });
|
||||
});
|
||||
|
||||
it("covers file validation, progress and AbortSignal cancellation", async () => {
|
||||
const adapter = new FakeFileTransferAdapter(10, new Set(["text/plain"]));
|
||||
const progress: number[] = [];
|
||||
const controller = new AbortController();
|
||||
const uploaded = await adapter.upload({
|
||||
file: {
|
||||
name: "safe.txt",
|
||||
size: 4,
|
||||
type: "text/plain",
|
||||
content: {
|
||||
byteLength: 4,
|
||||
chunks: (async function* () {
|
||||
yield new TextEncoder().encode("safe");
|
||||
})(),
|
||||
},
|
||||
},
|
||||
signal: controller.signal,
|
||||
onProgress(value) {
|
||||
progress.push(value.transferredBytes);
|
||||
},
|
||||
});
|
||||
expect(uploaded.ok).toBe(true);
|
||||
expect(progress).toEqual([4]);
|
||||
controller.abort();
|
||||
const cancelled = await adapter.download({
|
||||
resourceId: "one",
|
||||
signal: controller.signal,
|
||||
onProgress() {},
|
||||
});
|
||||
expect(cancelled).toMatchObject({
|
||||
ok: false,
|
||||
failure: { code: "ABORTED" },
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps generated API and feature flag vendors behind typed facades", async () => {
|
||||
const api = new FakeGeneratedApiAdapter("2026-07", {
|
||||
list: () => [{ id: "one" }],
|
||||
});
|
||||
expect(
|
||||
await api.execute<Array<{ id: string }>>({
|
||||
operationId: "list",
|
||||
contractVersion: "2026-07",
|
||||
}),
|
||||
).toEqual({ ok: true, value: [{ id: "one" }] });
|
||||
expect(
|
||||
(
|
||||
await api.execute({
|
||||
operationId: "list",
|
||||
contractVersion: "old",
|
||||
})
|
||||
).ok,
|
||||
).toBe(false);
|
||||
|
||||
const flags = new FakeFeatureFlagAdapter<{ checkout: boolean }>({
|
||||
checkout: true,
|
||||
});
|
||||
expect(
|
||||
await flags.evaluate({
|
||||
key: "checkout",
|
||||
fallback: false,
|
||||
maxAgeMs: 1_000,
|
||||
}),
|
||||
).toEqual({ ok: true, value: true });
|
||||
});
|
||||
|
||||
it("discards cancelled worker results and de-duplicates multi-tab events", async () => {
|
||||
const worker = new FakeWorkerTaskAdapter<number, number>((value) => value * 2);
|
||||
const controller = new AbortController();
|
||||
worker.cancel("cancelled");
|
||||
expect(
|
||||
(
|
||||
await worker.run({
|
||||
taskId: "cancelled",
|
||||
generation: 1,
|
||||
payload: 2,
|
||||
signal: controller.signal,
|
||||
})
|
||||
).ok,
|
||||
).toBe(false);
|
||||
|
||||
const tabs = new FakeMultiTabAdapter<{ refreshed: boolean }>();
|
||||
const observed: string[] = [];
|
||||
const cleanup = tabs.subscribe({
|
||||
sourceId: "tab-b",
|
||||
onEvent(result) {
|
||||
if (result.ok) observed.push(result.value.eventId);
|
||||
},
|
||||
});
|
||||
expect(
|
||||
tabs.publish({
|
||||
eventId: "one",
|
||||
sourceId: "tab-a",
|
||||
version: 1,
|
||||
payload: { refreshed: true },
|
||||
}).ok,
|
||||
).toBe(true);
|
||||
expect(
|
||||
tabs.publish({
|
||||
eventId: "one",
|
||||
sourceId: "tab-a",
|
||||
version: 1,
|
||||
payload: { refreshed: true },
|
||||
}).ok,
|
||||
).toBe(false);
|
||||
expect(observed).toEqual(["one"]);
|
||||
cleanup();
|
||||
tabs.close();
|
||||
});
|
||||
|
||||
it("separates browser permission, local workflow and large-data behavior", async () => {
|
||||
const permissions = new FakeBrowserPermissionAdapter({
|
||||
notification: "denied",
|
||||
});
|
||||
expect(
|
||||
await permissions.request({ capability: "notification" }),
|
||||
).toEqual({ ok: true, value: "denied" });
|
||||
expect(
|
||||
(
|
||||
await permissions.request({
|
||||
capability: "clipboard-read",
|
||||
})
|
||||
).ok,
|
||||
).toBe(false);
|
||||
|
||||
const workflow = new FakeClientWorkflowAdapter(
|
||||
{ step: 0 },
|
||||
(state, event: "next") => ({
|
||||
step: event === "next" ? state.step + 1 : state.step,
|
||||
}),
|
||||
);
|
||||
workflow.dispatch("next");
|
||||
expect(workflow.snapshot()).toEqual({ step: 1 });
|
||||
workflow.reset();
|
||||
expect(workflow.snapshot()).toEqual({ step: 0 });
|
||||
|
||||
const data = new FakeLargeDataUiAdapter<{ id: string; name: string }>();
|
||||
data.replace([{ id: "one", name: "row" }], 2);
|
||||
expect(data.window({ offset: 0, limit: 1, generation: 1 }).ok).toBe(false);
|
||||
expect(data.window({ offset: 0, limit: 1, generation: 2 })).toEqual({
|
||||
ok: true,
|
||||
value: [{ id: "one", name: "row" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("enforces analytics consent, redaction and bounded queues", () => {
|
||||
const adapter = new RecordingAnalyticsAdapter(1);
|
||||
expect(
|
||||
adapter.record({
|
||||
kind: "analytics",
|
||||
eventId: "page-viewed",
|
||||
consent: "denied",
|
||||
attributes: {},
|
||||
}).ok,
|
||||
).toBe(false);
|
||||
expect(
|
||||
adapter.record({
|
||||
kind: "error",
|
||||
eventId: "render-failed",
|
||||
consent: "not-required",
|
||||
attributes: { routeId: "HOME", authToken: "must-not-survive" },
|
||||
}).ok,
|
||||
).toBe(true);
|
||||
expect(adapter.records[0]?.attributes).toEqual({ routeId: "HOME" });
|
||||
expect(
|
||||
adapter.record({
|
||||
kind: "error",
|
||||
eventId: "second",
|
||||
consent: "not-required",
|
||||
attributes: {},
|
||||
}).ok,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("provides fail-closed unavailable adapters for every capability", async () => {
|
||||
const adapters = createUnavailableAdapters();
|
||||
const controller = new AbortController();
|
||||
const results = await Promise.all([
|
||||
adapters.realtime.heartbeat(),
|
||||
adapters.offline.open({ schemaVersion: 1 }),
|
||||
adapters.serviceWorker.unregister(),
|
||||
adapters.fileTransfer.download({
|
||||
resourceId: "one",
|
||||
signal: controller.signal,
|
||||
onProgress() {},
|
||||
}),
|
||||
adapters.generatedApi.execute({
|
||||
operationId: "one",
|
||||
contractVersion: "one",
|
||||
}),
|
||||
adapters.featureFlag.evaluate({
|
||||
key: "one",
|
||||
fallback: false,
|
||||
maxAgeMs: 0,
|
||||
}),
|
||||
adapters.worker.run({
|
||||
taskId: "one",
|
||||
generation: 1,
|
||||
payload: null,
|
||||
signal: controller.signal,
|
||||
}),
|
||||
adapters.browserPermission.request({ capability: "notification" }),
|
||||
adapters.analytics.flush(),
|
||||
]);
|
||||
expect(results.every((result) => !result.ok)).toBe(true);
|
||||
expect(adapters.multiTab.publish({
|
||||
eventId: "one",
|
||||
sourceId: "one",
|
||||
version: 1,
|
||||
payload: null,
|
||||
}).ok).toBe(false);
|
||||
expect(adapters.clientWorkflow.dispatch(null).ok).toBe(false);
|
||||
expect(
|
||||
adapters.largeDataUi.window({ offset: 0, limit: 1, generation: 1 }).ok,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user