mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 17:30:50 +00:00
138 lines
4.4 KiB
TypeScript
138 lines
4.4 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { act } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const openComposition = vi.fn();
|
|
|
|
vi.mock("@hyperframes/sdk", () => ({
|
|
openComposition: (...args: unknown[]) => openComposition(...args),
|
|
}));
|
|
|
|
import type { Composition } from "@hyperframes/sdk";
|
|
import { useSdkSession, type SdkSessionHandle } from "./useSdkSession";
|
|
|
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
function fakeSession(): Composition {
|
|
return { dispose: vi.fn() } as unknown as Composition;
|
|
}
|
|
|
|
function response(content: string): Response {
|
|
return { ok: true, json: async () => ({ content }) } as Response;
|
|
}
|
|
|
|
async function flushAsyncEffects(): Promise<void> {
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
});
|
|
}
|
|
|
|
describe("useSdkSession ownership", () => {
|
|
beforeEach(() => {
|
|
openComposition.mockReset();
|
|
class FakeEventSource {
|
|
addEventListener(): void {}
|
|
close(): void {}
|
|
}
|
|
vi.stubGlobal("EventSource", FakeEventSource);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("hides project A immediately while project B with the same path is still opening", async () => {
|
|
const sessionA = fakeSession();
|
|
const publishedA = fakeSession();
|
|
const sessionB = fakeSession();
|
|
let resolveProjectB: ((value: Response) => void) | undefined;
|
|
const projectBResponse = new Promise<Response>((resolve) => {
|
|
resolveProjectB = resolve;
|
|
});
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn((url: string) =>
|
|
url.includes("project-b") ? projectBResponse : Promise.resolve(response("PROJECT_A")),
|
|
),
|
|
);
|
|
openComposition.mockImplementation(async (content: string) =>
|
|
content === "PROJECT_A" ? sessionA : sessionB,
|
|
);
|
|
|
|
const captured: { handle: SdkSessionHandle | null } = { handle: null };
|
|
function Probe({ projectId }: { projectId: string }) {
|
|
captured.handle = useSdkSession(projectId, "index.html");
|
|
return null;
|
|
}
|
|
|
|
const root = createRoot(document.createElement("div"));
|
|
await act(async () => root.render(<Probe projectId="project-a" />));
|
|
await flushAsyncEffects();
|
|
expect(captured.handle?.session).toBe(sessionA);
|
|
|
|
let publication: ReturnType<SdkSessionHandle["publish"]> | undefined;
|
|
await act(async () => {
|
|
publication = captured.handle?.publish({
|
|
candidate: publishedA,
|
|
expectedSession: sessionA,
|
|
targetPath: "index.html",
|
|
});
|
|
});
|
|
expect(publication).toBe("published");
|
|
expect(captured.handle?.session).toBe(publishedA);
|
|
|
|
await act(async () => root.render(<Probe projectId="project-b" />));
|
|
expect(captured.handle?.session).toBeNull();
|
|
expect(publishedA.dispose).toHaveBeenCalledOnce();
|
|
expect(
|
|
captured.handle?.publish({
|
|
candidate: fakeSession(),
|
|
expectedSession: publishedA,
|
|
targetPath: "index.html",
|
|
}),
|
|
).toBe("rejected-inactive-target");
|
|
|
|
resolveProjectB?.(response("PROJECT_B"));
|
|
await flushAsyncEffects();
|
|
expect(captured.handle?.session).toBe(sessionB);
|
|
|
|
await act(async () => root.unmount());
|
|
expect(sessionB.dispose).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("disposes the currently published candidate when its owner unmounts", async () => {
|
|
const opened = fakeSession();
|
|
const published = fakeSession();
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(async () => response("PROJECT_A")),
|
|
);
|
|
openComposition.mockResolvedValue(opened);
|
|
|
|
const captured: { handle: SdkSessionHandle | null } = { handle: null };
|
|
function Probe() {
|
|
captured.handle = useSdkSession("project-a", "index.html");
|
|
return null;
|
|
}
|
|
|
|
const root = createRoot(document.createElement("div"));
|
|
await act(async () => root.render(<Probe />));
|
|
await flushAsyncEffects();
|
|
expect(captured.handle?.session).toBe(opened);
|
|
let publication: ReturnType<SdkSessionHandle["publish"]> | undefined;
|
|
await act(async () => {
|
|
publication = captured.handle?.publish({
|
|
candidate: published,
|
|
expectedSession: opened,
|
|
targetPath: "index.html",
|
|
});
|
|
});
|
|
expect(publication).toBe("published");
|
|
expect(opened.dispose).toHaveBeenCalledOnce();
|
|
|
|
await act(async () => root.unmount());
|
|
expect(published.dispose).toHaveBeenCalledOnce();
|
|
});
|
|
});
|