Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
type DesktopPreviewBridge,
} from "@t3tools/contracts";
import { act } from "react";
import { create, type ReactTestRenderer } from "react-test-renderer";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";

const mocks = vi.hoisted(() => ({
Expand All @@ -20,28 +20,32 @@ const mocks = vi.hoisted(() => ({
activeRecordings: new Set<string>(),
}));

vi.mock("~/localApi", () => ({
ensureLocalApi: () => ({ persistence: mocks }),
}));
vi.mock(import("~/localApi"), async (original) => {
const actual = await original();
return {
...actual,
ensureLocalApi: () => ({ ...actual.ensureLocalApi(), persistence: mocks as never }),
};
});

vi.mock(import("./desktopTabLifetime"), { spy: true });

vi.mock("~/components/preview/previewBridge", () => ({
// The Electron desktop bridge; a browser has none.
vi.mock(import("~/components/preview/previewBridge"), async (original) => ({
...(await original()),
previewBridge: {
createTab: mocks.createTab,
closeTab: mocks.closeTab,
registerWebview: mocks.registerWebview,
getPreviewConfig: mocks.getPreviewConfig,
},
} as unknown as DesktopPreviewBridge,
}));

vi.mock("~/components/preview/usePreviewBridge", () => ({
vi.mock(import("~/components/preview/usePreviewBridge"), async (original) => ({
...(await original()),
usePreviewBridge: () => undefined,
}));

vi.mock("./browserRecording", () => ({
useActiveBrowserRecordingTabIds: () => mocks.activeRecordings,
stopBrowserRecording: async () => null,
}));

import {
__resetClientSettingsPersistenceForTests,
ensureClientSettingsHydrated,
Expand All @@ -50,7 +54,8 @@ import { useBrowserSurfaceStore } from "./browserSurfaceStore";
import * as desktopTabLifetime from "./desktopTabLifetime";
import { HostedBrowserWebview } from "./HostedBrowserWebview";

let renderer: ReactTestRenderer | undefined;
let root: Root | undefined;
let container: HTMLDivElement;

function deferred<A>() {
let resolve!: (value: A) => void;
Expand All @@ -76,20 +81,16 @@ beforeEach(() => {
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);
container = document.createElement("div");
document.body.append(container);
});

afterEach(async () => {
vi.useFakeTimers();
await act(() => renderer?.unmount());
renderer = undefined;
await act(async () => root?.unmount());
root = undefined;
container.remove();
await vi.advanceTimersByTimeAsync(0);
vi.useRealTimers();
__resetClientSettingsPersistenceForTests();
Expand All @@ -107,19 +108,31 @@ describe("HostedBrowserWebview settings hydration", () => {
.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 acquire = vi.mocked(desktopTabLifetime.acquireDesktopTab);
// Chromium treats <webview> as an unknown element; give it the one Electron method the
// host calls once the guest attaches.
const createElement = document.createElement.bind(document);
const createGuest = vi.spyOn(document, "createElement").mockImplementation(((
tagName: string,
options?: ElementCreationOptions,
) => {
const element = createElement(tagName, options);
return tagName === "webview"
? Object.assign(element, { getWebContentsId: () => 41 })
: element;
}) as typeof document.createElement);
const guests = () => createGuest.mock.calls.filter(([tagName]) => tagName === "webview");
const guest = () => container.querySelector("webview");
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(
root = createRoot(container);
await act(async () =>
root!.render(
<HostedBrowserWebview
threadRef={threadRef}
tabId="server-tab"
Expand All @@ -130,18 +143,12 @@ describe("HostedBrowserWebview settings hydration", () => {
profileId="work"
zoomFactor={1.25}
/>,
{
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(guest()).toBeNull();
expect(mocks.createTab).not.toHaveBeenCalled();

const failure = new Error("Saved settings are unavailable");
Expand All @@ -151,7 +158,7 @@ describe("HostedBrowserWebview settings hydration", () => {
await expect(hydration).rejects.toBe(failure);
});
expect(acquire).not.toHaveBeenCalled();
expect(createGuest).not.toHaveBeenCalled();
expect(guest()).toBeNull();
expect(mocks.createTab).not.toHaveBeenCalled();

let retry!: Promise<void>;
Expand All @@ -160,7 +167,7 @@ describe("HostedBrowserWebview settings hydration", () => {
});
expect(mocks.getClientSettings).toHaveBeenCalledTimes(2);
expect(acquire).not.toHaveBeenCalled();
expect(createGuest).not.toHaveBeenCalled();
expect(guest()).toBeNull();
expect(mocks.createTab).not.toHaveBeenCalled();

await act(async () => {
Expand All @@ -176,13 +183,9 @@ describe("HostedBrowserWebview settings hydration", () => {

expect(acquire).toHaveBeenCalledExactlyOnceWith(runtimeTabId, undefined);
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(guests()).toHaveLength(1);
expect(guest()?.getAttribute("partition")).toBe("persist:t3-preview-work");
expect(guest()?.getAttribute("src")).toBe("https://example.com");
expect(mocks.createTab).toHaveBeenCalledExactlyOnceWith(runtimeTabId, {
zoomFactor: 1.25,
colorScheme: "dark",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import type { RelayManagedEndpointProviderKind } from "@t3tools/contracts/relay";
import { AsyncResult } from "effect/reactivity";
import { act, useLayoutEffect } from "react";
import { create, type ReactTestRenderer } from "react-test-renderer";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";

const testState = vi.hoisted(() => ({
Expand All @@ -24,8 +24,9 @@ const testState = vi.hoisted(() => ({
toast: vi.fn(),
}));

vi.mock("@effect/atom-react", () => ({
useAtomValue: () => ({
vi.mock(import("@effect/atom-react"), async (original) => ({
...(await original()),
useAtomValue: (() => ({
environments: new Map(
testState.relayProviderKind === null
? []
Expand All @@ -36,35 +37,56 @@ vi.mock("@effect/atom-react", () => ({
],
],
),
}),
})) as never,
}));
vi.mock("@clerk/react", () => ({
useAuth: () => ({ getToken: testState.getToken, isSignedIn: true }),
vi.mock(import("@clerk/react"), async (original) => ({
...(await original()),
useAuth: (() => ({ getToken: testState.getToken, isSignedIn: true })) as never,
}));
vi.mock("../components/ui/toast", () => ({ toastManager: { add: testState.toast } }));
vi.mock("../state/relay", () => ({
relayEnvironmentDiscovery: { refresh: testState.refreshDiscovery, stateValueAtom: {} },
vi.mock(import("../components/ui/toast"), async (original) => {
const actual = await original();
return { ...actual, toastManager: { ...actual.toastManager, add: testState.toast } as never };
});
vi.mock(import("../state/relay"), async (original) => {
const actual = await original();
return {
...actual,
relayEnvironmentDiscovery: {
...actual.relayEnvironmentDiscovery,
refresh: testState.refreshDiscovery,
} as never,
};
});
vi.mock(import("../state/session"), async (original) => ({
...(await original()),
readEnvironmentScope: (_environmentId, scope) => testState.scopes.has(scope),
}));
vi.mock("../state/session", () => ({
readEnvironmentScope: (_environmentId: string, scope: string) => testState.scopes.has(scope),
vi.mock(import("../state/use-atom-command"), async (original) => ({
...(await original()),
useAtomCommand: ((command: unknown) => command) as never,
}));
vi.mock("../state/use-atom-command", () => ({ useAtomCommand: (command: unknown) => command }));
vi.mock("./linkEnvironmentAtoms", () => ({
linkPrimaryEnvironment: testState.link,
unlinkPrimaryEnvironment: testState.unlink,
updatePrimaryEnvironmentPreferences: testState.preferences,
vi.mock(import("./linkEnvironmentAtoms"), async (original) => ({
...(await original()),
linkPrimaryEnvironment: testState.link as never,
unlinkPrimaryEnvironment: testState.unlink as never,
updatePrimaryEnvironmentPreferences: testState.preferences as never,
}));
vi.mock("./primaryCloudLinkState", () => ({
vi.mock(import("./primaryCloudLinkState"), async (original) => ({
...(await original()),
readCachedPrimaryCloudLinkState: () => testState.snapshot,
usePrimaryCloudLinkState: () => ({
usePrimaryCloudLinkState: (() => ({
target,
data: testState.renderedSnapshot,
error: null,
isPending: false,
refresh: testState.refreshLink,
}),
})) as never,
}));
// The Clerk JWT template is build-time config the test environment does not set.
vi.mock(import("./publicConfig"), async (original) => ({
...(await original()),
resolveRelayClerkTokenOptions: () => ({}) as never,
}));
vi.mock("./publicConfig", () => ({ resolveRelayClerkTokenOptions: () => ({}) }));

import { useCloudLinkController, type CloudLinkDesiredState } from "./useCloudLinkController";

Expand All @@ -83,7 +105,7 @@ const linkedState: EnvironmentCloudLinkStateResult = {
publishAgentActivity: false,
};

let renderer: ReactTestRenderer | null = null;
let root: Root | null = null;
let controller: ReturnType<typeof useCloudLinkController> | null = null;

function ControllerProbe() {
Expand All @@ -95,9 +117,8 @@ function ControllerProbe() {
}

async function mountController() {
await act(() => {
renderer = create(<ControllerProbe />);
});
root = createRoot(document.createElement("div"));
await act(async () => root!.render(<ControllerProbe />));
}

async function reconcile(desired: CloudLinkDesiredState) {
Expand Down Expand Up @@ -136,8 +157,8 @@ beforeEach(() => {
});

afterEach(async () => {
await act(() => renderer?.unmount());
renderer = null;
await act(async () => root?.unmount());
root = null;
controller = null;
vi.restoreAllMocks();
vi.unstubAllGlobals();
Expand Down
Loading
Loading