98 lines
3.6 KiB
TypeScript
98 lines
3.6 KiB
TypeScript
// @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" } },
|
|
}));
|
|
});
|
|
});
|