import { DEFAULT_CLIENT_SETTINGS, EnvironmentId, FILL_PREVIEW_VIEWPORT, ThreadId, type ClientSettings, type DesktopPreviewBridge, } from "@t3tools/contracts"; import { act } from "react"; import { create, type ReactTestRenderer } from "react-test-renderer"; import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; const mocks = vi.hoisted(() => ({ getClientSettings: vi.fn<() => Promise>(), setClientSettings: vi.fn<(settings: ClientSettings) => Promise>(), createTab: vi.fn(), closeTab: vi.fn(), registerWebview: vi.fn(), getPreviewConfig: vi.fn(), activeRecordings: new Set(), })); vi.mock("~/localApi", () => ({ ensureLocalApi: () => ({ persistence: mocks }), })); vi.mock("~/components/preview/previewBridge", () => ({ previewBridge: { createTab: mocks.createTab, closeTab: mocks.closeTab, registerWebview: mocks.registerWebview, getPreviewConfig: mocks.getPreviewConfig, }, })); vi.mock("~/components/preview/usePreviewBridge", () => ({ usePreviewBridge: () => undefined, })); vi.mock("./browserRecording", () => ({ useActiveBrowserRecordingTabIds: () => mocks.activeRecordings, stopBrowserRecording: async () => null, })); import { __resetClientSettingsPersistenceForTests, ensureClientSettingsHydrated, } from "~/hooks/useSettings"; import { useBrowserSurfaceStore } from "./browserSurfaceStore"; import * as desktopTabLifetime from "./desktopTabLifetime"; import { HostedBrowserWebview } from "./HostedBrowserWebview"; let renderer: ReactTestRenderer | undefined; function deferred() { let resolve!: (value: A) => void; let reject!: (error: unknown) => void; const promise = new Promise((resolvePromise, rejectPromise) => { resolve = resolvePromise; reject = rejectPromise; }); return { promise, resolve, reject }; } beforeEach(() => { __resetClientSettingsPersistenceForTests(); useBrowserSurfaceStore.setState({ activityByTabId: {}, byTabId: {} }); mocks.getClientSettings.mockReset(); mocks.setClientSettings.mockReset().mockResolvedValue(undefined); mocks.createTab.mockReset().mockResolvedValue(undefined); mocks.closeTab.mockReset().mockResolvedValue(undefined); mocks.registerWebview.mockReset().mockResolvedValue(undefined); mocks.getPreviewConfig.mockReset().mockResolvedValue({ partition: "persist:t3-preview-work", webPreferences: "contextIsolation=yes", preloadUrl: null, }); vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); vi.stubGlobal("window", globalThis); vi.stubGlobal("navigator", { platform: "Linux" }); vi.stubGlobal( "requestAnimationFrame", vi.fn(() => 0), ); vi.stubGlobal("cancelAnimationFrame", vi.fn()); vi.spyOn(console, "error").mockImplementation(() => undefined); }); afterEach(async () => { vi.useFakeTimers(); await act(() => renderer?.unmount()); renderer = undefined; await vi.advanceTimersByTimeAsync(0); vi.useRealTimers(); __resetClientSettingsPersistenceForTests(); useBrowserSurfaceStore.setState({ activityByTabId: {}, byTabId: {} }); vi.restoreAllMocks(); vi.unstubAllGlobals(); }); describe("HostedBrowserWebview settings hydration", () => { it("starts a retained background tab only after a settings read succeeds on retry", async () => { const firstRead = deferred(); const retryRead = deferred(); const tabCreation = deferred(); mocks.getClientSettings .mockReturnValueOnce(firstRead.promise) .mockReturnValueOnce(retryRead.promise); mocks.createTab.mockReturnValueOnce(tabCreation.promise); const acquire = vi.spyOn(desktopTabLifetime, "acquireDesktopTab"); const createGuest = vi.fn((_attributes: unknown) => Object.assign(new EventTarget(), { getWebContentsId: () => 41 }), ); const threadRef = { environmentId: EnvironmentId.make("host-settings-retry"), threadId: ThreadId.make("thread-settings-retry"), }; const runtimeTabId = "retained-background-tab"; useBrowserSurfaceStore.getState().acquireActivity(runtimeTabId); await act(() => { renderer = create( , { createNodeMock: (element) => element.type === "webview" ? createGuest(element.props) : { scrollLeft: 0, scrollTop: 0, scrollTo: () => undefined }, }, ); }); expect(mocks.getClientSettings).toHaveBeenCalledOnce(); expect(acquire).not.toHaveBeenCalled(); expect(createGuest).not.toHaveBeenCalled(); expect(mocks.createTab).not.toHaveBeenCalled(); const failure = new Error("Saved settings are unavailable"); await act(async () => { const hydration = ensureClientSettingsHydrated(); firstRead.reject(failure); await expect(hydration).rejects.toBe(failure); }); expect(acquire).not.toHaveBeenCalled(); expect(createGuest).not.toHaveBeenCalled(); expect(mocks.createTab).not.toHaveBeenCalled(); let retry!: Promise; await act(() => { retry = ensureClientSettingsHydrated(); }); expect(mocks.getClientSettings).toHaveBeenCalledTimes(2); expect(acquire).not.toHaveBeenCalled(); expect(createGuest).not.toHaveBeenCalled(); expect(mocks.createTab).not.toHaveBeenCalled(); await act(async () => { retryRead.resolve({ ...DEFAULT_CLIENT_SETTINGS, browserDefaultZoomFactor: 1.25, browserDefaultAppearance: "dark", browserProfiles: [{ id: "work", name: "Work", kind: "persistent" }], browserDefaultProfileId: "work", }); await retry; }); expect(acquire).toHaveBeenCalledExactlyOnceWith(runtimeTabId); expect(mocks.getPreviewConfig).toHaveBeenCalledExactlyOnceWith(threadRef.environmentId, "work"); expect(createGuest).toHaveBeenCalledOnce(); expect(createGuest).toHaveBeenCalledWith( expect.objectContaining({ partition: "persist:t3-preview-work", src: "https://example.com", }), ); expect(mocks.createTab).toHaveBeenCalledExactlyOnceWith(runtimeTabId, { zoomFactor: 1.25, colorScheme: "dark", }); expect(mocks.registerWebview).not.toHaveBeenCalled(); await act(async () => { tabCreation.resolve(); await tabCreation.promise; }); expect(mocks.registerWebview).toHaveBeenCalledExactlyOnceWith(runtimeTabId, 41); expect(mocks.closeTab).not.toHaveBeenCalled(); expect(mocks.setClientSettings).not.toHaveBeenCalled(); }); });