import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { render, cleanup, act } from "@testing-library/react"; import { createRef } from "react"; import { Fullpage } from "./Fullpage"; import type { EmbedHandle } from "@perspective-ai/sdk"; const mockUnmount = vi.fn(); const mockUpdate = vi.fn(); // Stable reference so useEmbedConfig doesn't trigger extra effect runs const mockEmbedConfig = { primaryColor: "#7c3aed", textColor: "#ffffff", darkPrimaryColor: "#a78bfa", darkTextColor: "#ffffff", }; vi.mock("@perspective-ai/sdk", () => ({ createFullpage: vi.fn(() => ({ unmount: mockUnmount, update: mockUpdate, destroy: mockUnmount, researchId: "test-research-id", type: "fullpage", iframe: null, container: null, })), fetchEmbedConfig: vi.fn(() => Promise.resolve(mockEmbedConfig)), createLoadingIndicator: vi.fn(() => { const el = document.createElement("div"); el.className = "perspective-loading"; return el; }), })); import { createFullpage } from "@perspective-ai/sdk"; const mockCreateFullpage = vi.mocked(createFullpage); describe("Fullpage", () => { beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { cleanup(); }); it("renders only attribution metadata (fullpage overlay is added to document.body)", async () => { const { container } = render(); await act(async () => {}); expect( container.querySelector("script[data-perspective-jsonld]") ).toBeTruthy(); // No visible DOM elements — only the JSON-LD script tag expect(container.querySelectorAll(":not(script)")).toHaveLength(0); }); it("calls createFullpage on mount", async () => { render(); await act(async () => {}); expect(mockCreateFullpage).toHaveBeenCalledTimes(1); expect(mockCreateFullpage).toHaveBeenCalledWith( expect.objectContaining({ researchId: "test-research-id", }) ); }); it("calls unmount on cleanup", async () => { const { unmount } = render(); await act(async () => {}); expect(mockUnmount).not.toHaveBeenCalled(); unmount(); expect(mockUnmount).toHaveBeenCalledTimes(1); }); it("passes config to createFullpage", async () => { const onReady = vi.fn(); const onSubmit = vi.fn(); const onClose = vi.fn(); const onNavigate = vi.fn(); const onError = vi.fn(); render( ); await act(async () => {}); expect(mockCreateFullpage).toHaveBeenCalledTimes(1); const config = mockCreateFullpage.mock.calls[0]![0]; expect(config.researchId).toBe("test-research-id"); expect(config.params).toEqual({ source: "test" }); expect(config.theme).toBe("dark"); expect(config.host).toBe("https://custom.example.com"); }); it("shows skeleton immediately, creates fullpage after config loads", async () => { render(); expect(mockCreateFullpage).not.toHaveBeenCalled(); await act(async () => {}); expect(mockCreateFullpage).toHaveBeenCalledTimes(1); }); it("exposes handle via embedRef", async () => { const mockHandle: EmbedHandle = { unmount: mockUnmount, update: mockUpdate, destroy: mockUnmount, researchId: "test-research-id", type: "fullpage", iframe: null, container: null, }; mockCreateFullpage.mockReturnValueOnce(mockHandle); const embedRef = createRef(); render(); await act(async () => {}); expect(embedRef.current).toBe(mockHandle); }); it("clears embedRef on unmount", async () => { const embedRef = createRef(); const { unmount } = render( ); await act(async () => {}); expect(embedRef.current).not.toBeNull(); unmount(); expect(embedRef.current).toBeNull(); }); it("re-creates fullpage when researchId changes", async () => { const { rerender } = render(); await act(async () => {}); expect(mockCreateFullpage).toHaveBeenCalledTimes(1); rerender(); await act(async () => {}); expect(mockUnmount).toHaveBeenCalledTimes(1); expect(mockCreateFullpage).toHaveBeenCalledTimes(2); }); it("re-creates fullpage when theme changes", async () => { const { rerender } = render( ); await act(async () => {}); expect(mockCreateFullpage).toHaveBeenCalledTimes(1); rerender(); await act(async () => {}); expect(mockUnmount).toHaveBeenCalledTimes(1); expect(mockCreateFullpage).toHaveBeenCalledTimes(2); }); it("passes brand colors to createFullpage", async () => { render( ); await act(async () => {}); const config = mockCreateFullpage.mock.calls[0]![0]; expect(config.brand).toEqual({ light: { primary: "#ff0000", bg: "#ffffff" }, dark: { primary: "#0000ff", bg: "#000000" }, }); }); });