import type { AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; import type { PreviewSessionSnapshot, ScopedThreadRef } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; import { AsyncResult } from "effect/unstable/reactivity"; import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { readThreadPreviewState, resetPreviewStateForTests } from "~/previewStateStore"; import { selectThreadRightPanelState, useRightPanelStore } from "~/rightPanelStore"; import { BrowserPreviewUnavailableError, isBrowserPreviewAssetUrlInvalidError, type OpenPreviewMutation, openFileInPreview, openUrlInPreview, } from "./openFileInPreview"; const threadRef = { environmentId: "environment-1" as ScopedThreadRef["environmentId"], threadId: "thread-1" as ScopedThreadRef["threadId"], }; const snapshot = (tabId: string, url: string): PreviewSessionSnapshot => ({ threadId: threadRef.threadId, tabId, navStatus: { _tag: "Success", url, title: "" }, canGoBack: false, canGoForward: false, updatedAt: "2026-06-21T00:00:00.000Z", }); beforeEach(() => { resetPreviewStateForTests(); useRightPanelStore.setState({ byThreadKey: {} }); }); afterEach(() => vi.unstubAllGlobals()); describe("openFileInPreview", () => { it("uses the fixed unavailable-runtime message", () => { expect(new BrowserPreviewUnavailableError().message).toBe( "The integrated browser is unavailable in this runtime.", ); }); it("reports invalid asset URLs with safe context and the exact parser cause", async () => { vi.stubGlobal("window", { desktopBridge: { preview: {} } }); const parserCause = new TypeError("invalid URL"); const InvalidUrl = vi.fn(function InvalidUrl() { throw parserCause; }); vi.stubGlobal("URL", InvalidUrl); const openPreview = vi.fn(); const httpBaseUrl = "not a URL"; const relativeUrl = "/api/assets/signed-secret-token/docs/report.pdf"; const expiresAt = Date.now(); const result = await openFileInPreview({ threadRef, filePath: "docs/report.pdf", httpBaseUrl, createAssetUrl: async () => AsyncResult.success({ relativeUrl, expiresAt }), openPreview, }); const error = result._tag === "Failure" ? Cause.squash(result.cause) : undefined; expect(isBrowserPreviewAssetUrlInvalidError(error)).toBe(true); if (!isBrowserPreviewAssetUrlInvalidError(error)) { throw new Error("Expected BrowserPreviewAssetUrlInvalidError"); } expect(error).toMatchObject({ environmentId: "environment-1", threadId: "thread-1", filePath: "docs/report.pdf", httpBaseUrlLength: httpBaseUrl.length, relativeUrlLength: relativeUrl.length, expiresAt, }); expect(error.cause).toBe(parserCause); expect(error.message).toBe("The environment returned an invalid asset URL."); expect(error).not.toHaveProperty("httpBaseUrl"); expect(error).not.toHaveProperty("relativeUrl"); expect(JSON.stringify(error)).not.toContain("signed-secret-token"); expect(openPreview).not.toHaveBeenCalled(); }); }); it("does not apply an older preview response after another caller starts a newer request", async () => { const firstSnapshot = snapshot("tab-1", "https://assets.test/first.png"); const secondSnapshot = snapshot("tab-2", "https://assets.test/second.png"); let resolveFirst!: (result: AtomCommandResult) => void; const openPreview: OpenPreviewMutation = ({ input }) => input.url === "https://assets.test/first.png" ? new Promise>((resolve) => { resolveFirst = resolve; }) : Promise.resolve(AsyncResult.success(secondSnapshot)); const firstRequest = openUrlInPreview({ threadRef, url: "https://assets.test/first.png", openPreview, }); await openUrlInPreview({ threadRef, url: "https://assets.test/second.png", openPreview, }); resolveFirst(AsyncResult.success(firstSnapshot)); await firstRequest; expect(readThreadPreviewState(threadRef).snapshot).toEqual(secondSnapshot); expect( selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, threadRef).surfaces, ).toEqual([{ id: "browser:tab-2", kind: "preview", resourceId: "tab-2" }]); });