Browse Source

test(auth): update login page tests for redesigned UI

ding113 2 weeks ago
parent
commit
4449554d80

+ 1 - 1
tests/unit/i18n/auth-login-keys.test.ts

@@ -38,7 +38,7 @@ describe("auth.json locale key parity", () => {
   it("English baseline has expected top-level sections", () => {
     const topLevel = Object.keys(enAuth).sort();
     expect(topLevel).toEqual(
-      ["actions", "errors", "form", "login", "logout", "placeholders", "security"].sort()
+      ["actions", "brand", "errors", "form", "login", "logout", "placeholders", "security"].sort()
     );
   });
 

+ 4 - 0
tests/unit/login/login-footer-system-name.test.tsx

@@ -28,6 +28,10 @@ vi.mock("@/i18n/routing", () => ({
   usePathname: mockUsePathname,
 }));
 
+vi.mock("next-themes", () => ({
+  useTheme: vi.fn(() => ({ theme: "system", setTheme: vi.fn() })),
+}));
+
 const globalFetch = global.fetch;
 const DEFAULT_SITE_TITLE = "Claude Code Hub";
 

+ 4 - 0
tests/unit/login/login-footer-version.test.tsx

@@ -28,6 +28,10 @@ vi.mock("@/i18n/routing", () => ({
   usePathname: mockUsePathname,
 }));
 
+vi.mock("next-themes", () => ({
+  useTheme: vi.fn(() => ({ theme: "system", setTheme: vi.fn() })),
+}));
+
 const globalFetch = global.fetch;
 
 describe("LoginPage Footer Version", () => {

+ 4 - 0
tests/unit/login/login-loading-state.test.tsx

@@ -28,6 +28,10 @@ vi.mock("@/i18n/routing", () => ({
   usePathname: mockUsePathname,
 }));
 
+vi.mock("next-themes", () => ({
+  useTheme: vi.fn(() => ({ theme: "system", setTheme: vi.fn() })),
+}));
+
 const globalFetch = global.fetch;
 
 describe("LoginPage Loading State", () => {

+ 4 - 0
tests/unit/login/login-overlay-a11y.test.tsx

@@ -28,6 +28,10 @@ vi.mock("@/i18n/routing", () => ({
   usePathname: mockUsePathname,
 }));
 
+vi.mock("next-themes", () => ({
+  useTheme: vi.fn(() => ({ theme: "system", setTheme: vi.fn() })),
+}));
+
 const globalFetch = global.fetch;
 
 describe("LoginPage Accessibility", () => {

+ 147 - 0
tests/unit/login/login-ui-redesign.test.tsx

@@ -0,0 +1,147 @@
+import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
+import { createRoot } from "react-dom/client";
+import { act } from "react";
+import LoginPage from "@/app/[locale]/login/page";
+
+const mockPush = vi.hoisted(() => vi.fn());
+const mockRefresh = vi.hoisted(() => vi.fn());
+const mockUseRouter = vi.hoisted(() => vi.fn(() => ({ push: mockPush, refresh: mockRefresh })));
+const mockUseSearchParams = vi.hoisted(() => vi.fn(() => ({ get: vi.fn(() => null) })));
+const mockUseTranslations = vi.hoisted(() => vi.fn(() => (key: string) => `t:${key}`));
+const mockUseLocale = vi.hoisted(() => vi.fn(() => "en"));
+const mockUsePathname = vi.hoisted(() => vi.fn(() => "/login"));
+
+vi.mock("next/navigation", () => ({
+  useSearchParams: mockUseSearchParams,
+  useRouter: mockUseRouter,
+  usePathname: mockUsePathname,
+}));
+
+vi.mock("next-intl", () => ({
+  useTranslations: mockUseTranslations,
+  useLocale: mockUseLocale,
+}));
+
+vi.mock("@/i18n/routing", () => ({
+  Link: ({ children, ...props }: any) => <a {...props}>{children}</a>,
+  useRouter: mockUseRouter,
+  usePathname: mockUsePathname,
+}));
+
+vi.mock("next-themes", () => ({
+  useTheme: vi.fn(() => ({ theme: "system", setTheme: vi.fn() })),
+}));
+
+describe("LoginPage UI Redesign", () => {
+  let container: HTMLDivElement;
+  let root: ReturnType<typeof createRoot>;
+
+  beforeEach(() => {
+    container = document.createElement("div");
+    document.body.appendChild(container);
+    root = createRoot(container);
+    vi.clearAllMocks();
+    global.fetch = vi.fn().mockResolvedValue({
+      ok: true,
+      json: async () => ({}),
+    });
+  });
+
+  afterEach(() => {
+    act(() => {
+      root.unmount();
+    });
+    document.body.removeChild(container);
+  });
+
+  const render = async () => {
+    await act(async () => {
+      root.render(<LoginPage />);
+    });
+  };
+
+  it("password toggle changes input type between password and text", async () => {
+    await render();
+
+    const input = container.querySelector("input#apiKey") as HTMLInputElement;
+    expect(input).not.toBeNull();
+    expect(input.type).toBe("password");
+
+    const toggleButton = container.querySelector(
+      'button[aria-label="t:form.showPassword"]'
+    ) as HTMLButtonElement;
+    expect(toggleButton).not.toBeNull();
+
+    await act(async () => {
+      toggleButton.click();
+    });
+
+    expect(input.type).toBe("text");
+
+    const hideButton = container.querySelector(
+      'button[aria-label="t:form.hidePassword"]'
+    ) as HTMLButtonElement;
+    expect(hideButton).not.toBeNull();
+
+    await act(async () => {
+      hideButton.click();
+    });
+
+    expect(input.type).toBe("password");
+  });
+
+  it("ThemeSwitcher renders in the top-right control area", async () => {
+    await render();
+
+    const topRightArea = container.querySelector(".fixed.top-4.right-4");
+    expect(topRightArea).not.toBeNull();
+
+    const buttons = topRightArea?.querySelectorAll("button");
+    expect(buttons?.length).toBeGreaterThanOrEqual(2);
+  });
+
+  it("brand panel has data-testid login-brand-panel", async () => {
+    await render();
+
+    const brandPanel = container.querySelector('[data-testid="login-brand-panel"]');
+    expect(brandPanel).not.toBeNull();
+  });
+
+  it("brand panel is hidden on mobile (has hidden class without lg:flex)", async () => {
+    await render();
+
+    const brandPanel = container.querySelector('[data-testid="login-brand-panel"]');
+    expect(brandPanel).not.toBeNull();
+    expect(brandPanel?.className).toContain("hidden");
+    expect(brandPanel?.className).toContain("lg:flex");
+  });
+
+  it("mobile brand header is visible on mobile (has lg:hidden class)", async () => {
+    await render();
+
+    const formPanel = container.querySelector(".lg\\:w-\\[55\\%\\]");
+    expect(formPanel).not.toBeNull();
+
+    const mobileHeader = formPanel?.querySelector(".lg\\:hidden");
+    expect(mobileHeader).not.toBeNull();
+  });
+
+  it("card header icon is hidden on desktop (has lg:hidden class)", async () => {
+    await render();
+
+    const card = container.querySelector('[data-slot="card"]');
+    expect(card).not.toBeNull();
+
+    const headerIcon = card?.querySelector(".lg\\:hidden");
+    expect(headerIcon).not.toBeNull();
+  });
+
+  it("input has padding for both key icon and toggle button", async () => {
+    await render();
+
+    const input = container.querySelector("input#apiKey") as HTMLInputElement;
+    expect(input).not.toBeNull();
+    expect(input.className).toContain("pl-9");
+    expect(input.className).toContain("pr-10");
+  });
+});

+ 4 - 0
tests/unit/login/login-visual-regression.test.tsx

@@ -29,6 +29,10 @@ vi.mock("@/i18n/routing", () => ({
   usePathname: mockUsePathname,
 }));
 
+vi.mock("next-themes", () => ({
+  useTheme: vi.fn(() => ({ theme: "system", setTheme: vi.fn() })),
+}));
+
 describe("LoginPage Visual Regression", () => {
   let container: HTMLDivElement;
   let root: ReturnType<typeof createRoot>;