From 8e1a1b56a4590725534d953560d736621274122c Mon Sep 17 00:00:00 2001 From: Amr Gaber Date: Sat, 23 May 2026 10:44:56 -0500 Subject: [PATCH] test(navbar): guard /auth/me display_name rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Locks down the JSON field name, the AuthProvider state-setter chain, and the navbar fallback so the recurring "navbar shows steam id" symptom can't silently come back through a frontend refactor. The actual cause of #23 lives in the auth API (see ag-tech-group/criticalbit-auth-api#26); this is the consumer-side guard. Two supporting changes the new test depends on: - Export `server` from src/test/setup.ts so tests can override MSW handlers per-test via `server.use(...)`. - Pin VITE_API_URL to an absolute URL in vitest.config.ts. Without it ky/undici reject the relative /api/... URL as unparseable, MSW never intercepts, and auth.tsx's try/catch swallows the error — every /auth/me call was silently failing in tests. Verified the guard catches the regression via mutation: renaming `display_name` to `displayName` in auth.tsx makes the test fail loudly. Refs #23. --- src/components/navbar.test.tsx | 67 ++++++++++++++++++++++++++++++++++ src/test/setup.ts | 2 +- vitest.config.ts | 2 + 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/components/navbar.test.tsx diff --git a/src/components/navbar.test.tsx b/src/components/navbar.test.tsx new file mode 100644 index 0000000..217252b --- /dev/null +++ b/src/components/navbar.test.tsx @@ -0,0 +1,67 @@ +import { screen } from "@testing-library/react" +import { http, HttpResponse } from "msw" +import { describe, expect, it } from "vitest" +import { renderWithFileRoutes } from "@/test/renderers" +import { server } from "@/test/setup" + +// Regression guard for issue #23 (recurring): the navbar must render the +// human-readable display_name returned by /auth/me, never the raw user id +// (which for Steam OAuth is the SteamID64). The actual root cause of the +// linked bug lives in the auth API — but this test pins the *frontend* +// contract so a rename like `display_name` → `displayName`, a dropped +// setDisplayName call, or a navbar swap to userId/email can't silently +// reintroduce the same visible regression here. + +const STEAM_ID = "76561198000000000" + +function authMeHandler(body: { + id: string + email: string + display_name: string | null + avatar_url: string | null +}) { + return http.get("*/auth/me", () => HttpResponse.json(body)) +} + +// The consent fetch fires right after /auth/me succeeds. Stub it so the +// AuthProvider's catch-all doesn't surface as an unhandled-request warning. +const consentsHandler = http.get("*/user/consents", () => + HttpResponse.json({ current_policy_version: "test", consents: {} }) +) + +describe("Navbar", () => { + it("renders the Steam display_name, not the SteamID64", async () => { + server.use( + authMeHandler({ + id: STEAM_ID, + email: "player@example.com", + display_name: "ZeroEmpires", + avatar_url: null, + }), + consentsHandler + ) + + await renderWithFileRoutes(<>) + + expect(await screen.findByText("ZeroEmpires")).toBeInTheDocument() + expect(screen.queryByText(STEAM_ID)).not.toBeInTheDocument() + expect(screen.queryByText("player@example.com")).not.toBeInTheDocument() + }) + + it("falls back to email when display_name is null", async () => { + server.use( + authMeHandler({ + id: STEAM_ID, + email: "player@example.com", + display_name: null, + avatar_url: null, + }), + consentsHandler + ) + + await renderWithFileRoutes(<>) + + expect(await screen.findByText("player@example.com")).toBeInTheDocument() + expect(screen.queryByText(STEAM_ID)).not.toBeInTheDocument() + }) +}) diff --git a/src/test/setup.ts b/src/test/setup.ts index b925197..4bfc6d4 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -4,7 +4,7 @@ import { cleanup } from "@testing-library/react" import { setupServer } from "msw/node" import { afterAll, afterEach, beforeAll, vi } from "vitest" -const server = setupServer(...handlers) +export const server = setupServer(...handlers) beforeAll(() => server.listen()) afterAll(() => server.close()) diff --git a/vitest.config.ts b/vitest.config.ts index 2ff9275..823edde 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,6 +9,8 @@ export default defineConfig({ environment: "jsdom", env: { VITE_LOG_LEVEL: "warn", + // Absolute URL so ky/MSW can parse it; the host is fake — MSW intercepts. + VITE_API_URL: "http://localhost/api", }, setupFiles: ["./src/test/setup.ts"], include: ["src/**/*.{test,spec}.{ts,tsx}"],