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}"],