import { act, render } from "@testing-library/react"; import { createRef } from "react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { TurnstileWidget, type TurnstileWidgetHandle } from "@/components/ui/turnstile-widget"; afterEach(() => { delete (window as unknown as { turnstile?: unknown }).turnstile; }); describe("TurnstileWidget", () => { it("renders nothing when no site key is configured", () => { const { container } = render( {}} />); expect(container).toBeEmptyDOMElement(); }); it("renders via window.turnstile once the script loads, reports tokens, and resets", () => { let capturedCallback: ((token: string) => void) | undefined; const renderMock = vi.fn( (_container: HTMLElement, options: { callback: (t: string) => void }) => { capturedCallback = options.callback; return "widget-1"; }, ); const resetMock = vi.fn(); const removeMock = vi.fn(); window.turnstile = { render: renderMock, reset: resetMock, remove: removeMock }; const onToken = vi.fn(); const ref = createRef(); render(); // jsdom never actually executes the remote Cloudflare script - simulate // next/script's load detection by firing "load" on the tag it inserted. const scriptEl = document.querySelector('script[src*="turnstile"]'); expect(scriptEl).not.toBeNull(); act(() => { scriptEl?.dispatchEvent(new Event("load")); }); expect(renderMock).toHaveBeenCalledTimes(1); expect(renderMock.mock.calls[0]?.[1]).toMatchObject({ sitekey: "test-site-key", action: "turnstile-spin-v2", }); capturedCallback?.("fake-token"); expect(onToken).toHaveBeenCalledWith("fake-token"); act(() => { ref.current?.reset(); }); expect(resetMock).toHaveBeenCalledWith("widget-1"); }); });