// @vitest-environment happy-dom import { act, createElement } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ social: vi.fn(), push: vi.fn(), replace: vi.fn(), refresh: vi.fn() })); vi.mock("next/navigation", () => ({ useRouter: () => ({ push: mocks.push, replace: mocks.replace, refresh: mocks.refresh, }) })); vi.mock("@/lib/auth/client", () => ({ authClient: { signIn: { social: mocks.social }, signUp: { email: vi.fn() }, emailOtp: { sendVerificationOtp: vi.fn(), verifyEmail: vi.fn() }, } })); import { AccountForm } from "./account-form"; let container: HTMLDivElement; let root: Root; beforeEach(() => { vi.clearAllMocks(); vi.stubEnv("NODE_ENV", "development"); vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); mocks.social.mockResolvedValue({ error: null }); container = document.createElement("div"); document.body.append(container); root = createRoot(container); }); afterEach(async () => { await act(async () => root.unmount()); container.remove(); Reflect.deleteProperty(window, "turnstile"); vi.unstubAllEnvs(); vi.unstubAllGlobals(); }); async function mount(mode: "login" | "register", nextPath = "/commission/tickets?from=login", googleEnabled = true) { await act(async () => root.render(createElement(AccountForm, { mode, nextPath, googleEnabled, siteKey: "test-site-key", }))); } function googleButton() { return [...container.querySelectorAll("button")].find((button) => button.textContent?.includes("Google")); } describe("Google sign-in form action", () => { it("offers Google sign-in on login with safe existing-user and new-user callbacks", async () => { await mount("login"); const button = googleButton(); expect(button).toBeDefined(); expect(button?.querySelector("img")?.getAttribute("src")).toBe("/icon/google.svg"); expect(button?.nextElementSibling?.textContent).toMatch(/สมัครสมาชิก|มีบัญชีแล้ว/); await act(async () => button!.click()); expect(mocks.social).toHaveBeenCalledWith(expect.objectContaining({ provider: "google", callbackURL: "/commission/tickets?from=login", newUserCallbackURL: "/auth/register?step=profile&next=%2Fcommission%2Ftickets%3Ffrom%3Dlogin", })); }); it("hides Google sign-in on registration", async () => { await mount("register"); expect(googleButton()).toBeUndefined(); expect(mocks.social).not.toHaveBeenCalled(); }); it("hides the provider action when Google credentials are not configured", async () => { await mount("login", "/", false); expect(googleButton()).toBeUndefined(); expect(mocks.social).not.toHaveBeenCalled(); }); it("passes the Turnstile token to the protected social sign-in endpoint", async () => { vi.stubEnv("NODE_ENV", "production"); const turnstile = { render: vi.fn((_container: HTMLElement, options: { callback: (token: string) => void }) => { options.callback("turnstile-token"); return "widget-id"; }), remove: vi.fn(), reset: vi.fn(), }; Object.defineProperty(window, "turnstile", { configurable: true, value: turnstile }); await mount("login", "/profile"); const button = googleButton(); expect(button?.disabled).toBe(false); await act(async () => button!.click()); expect(mocks.social).toHaveBeenCalledWith(expect.objectContaining({ provider: "google", fetchOptions: { headers: { "x-captcha-response": "turnstile-token" } }, })); }); });