import { describe, expect, it, vi } from "vitest"; import { defineToolcraft } from "../../schema/define-toolcraft"; import { createToolcraftState } from "../../state/create-template-state"; import { createToolcraftExternalStore, type ToolcraftExternalStore, } from "../../state/toolcraft-external-store"; import type { ToolcraftCommand } from "../../state/types"; import { createTestSourceAssetCoordinator } from "./toolcraft-source-asset-test-support"; import { decorateToolcraftUnavailableResourceProofCoordinator } from "./toolcraft-unavailable-resource-proof"; const PROOF_REQUEST = "toolcraft.browser-proof.unavailable-resource-request"; const RESOURCE_REF = `media:image:sha256:${"d".repeat(64)}`; function createDeferred() { let resolve!: (value: T) => void; const promise = new Promise((resolvePromise) => { resolve = resolvePromise; }); return { promise, resolve }; } function createReadyImageStore(): ToolcraftExternalStore { return createToolcraftExternalStore( createToolcraftState( defineToolcraft({ canvas: { enabled: true }, panels: {} }), { mediaAssets: [{ assetKind: "image", fileName: "fixture.png", id: "fixture-image", layerId: "fixture-layer", lifecycle: "ready", mimeType: "image/png", position: { x: 0, y: 0 }, resourceRef: RESOURCE_REF, size: { height: 20, unit: "px", width: 20 }, sourceTarget: "source.images", }], }, ), ); } function dispatchProofRequest(action: "activate" | "cleanup"): void { document.dispatchEvent(new CustomEvent(PROOF_REQUEST, { detail: { action, fileName: "fixture.png", requestId: "proof-request", sourceTarget: "source.images", }, })); } describe("Toolcraft unavailable-resource proof coordinator", () => { it("settles an aborted pending request without a post-dispose transition", async () => { const deferredBytes = createDeferred(); const baseDispose = vi.fn(async () => undefined); const resolveResource = vi.fn( ( _ref: string, _options: Readonly<{ signal: AbortSignal }>, ) => deferredBytes.promise, ); const backingStore = createReadyImageStore(); const dispatch = vi.fn(backingStore.dispatch); const store: ToolcraftExternalStore = { ...backingStore, dispatch }; const coordinator = decorateToolcraftUnavailableResourceProofCoordinator( createTestSourceAssetCoordinator({ dispose: baseDispose, resolveResource, }), store, ); dispatchProofRequest("activate"); await vi.waitFor(() => expect(resolveResource).toHaveBeenCalledTimes(1)); const disposal = coordinator.dispose(); deferredBytes.resolve(new Uint8Array([1, 2, 3])); await disposal; await Promise.resolve(); await Promise.resolve(); await Promise.resolve(); expect(store.getCommittedState().mediaAssets[0]).toMatchObject({ lifecycle: "ready", }); expect(resolveResource.mock.calls[0]?.[1].signal.aborted).toBe(true); expect(dispatch).not.toHaveBeenCalled(); expect(baseDispose).toHaveBeenCalledTimes(1); }); it("attempts base disposal when proof restoration dispatch fails", async () => { const dispatchFailure = new Error("proof restoration dispatch failed"); const baseFailure = new Error("base coordinator disposal failed"); const baseDispose = vi.fn(async () => Promise.reject(baseFailure)); const backingStore = createReadyImageStore(); const store: ToolcraftExternalStore = { ...backingStore, dispatch: vi.fn((command: ToolcraftCommand) => { if ( command.type === "media.setBinaryResourceState" && command.lifecycle === "ready" ) { throw dispatchFailure; } backingStore.dispatch(command); }), }; const coordinator = decorateToolcraftUnavailableResourceProofCoordinator( createTestSourceAssetCoordinator({ dispose: baseDispose, resolveResource: vi.fn(async () => new Uint8Array([1, 2, 3])), }), store, ); dispatchProofRequest("activate"); await vi.waitFor(() => expect(store.getCommittedState().mediaAssets[0]).toMatchObject({ lifecycle: "unavailable", }) ); const disposal = coordinator.dispose(); const repeatedDisposal = coordinator.dispose(); await expect(disposal).rejects.toMatchObject({ errors: [dispatchFailure, baseFailure], }); expect(repeatedDisposal).toBe(disposal); await expect(repeatedDisposal).rejects.toBeInstanceOf(AggregateError); expect(baseDispose).toHaveBeenCalledTimes(1); }); });