diff --git a/src/screens/__tests__/past-journeys-mode-icon-wiring.test.tsx b/src/screens/__tests__/past-journeys-mode-icon-wiring.test.tsx new file mode 100644 index 0000000..d22cb73 --- /dev/null +++ b/src/screens/__tests__/past-journeys-mode-icon-wiring.test.tsx @@ -0,0 +1,106 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { MemoryRouter } from "react-router-dom"; +import { ConnectScreen } from "@/screens/ConnectScreen"; +import { appendHistory } from "@/screens/lobby/historyStore"; +import type { GenresResponse } from "@/types/genres"; + +/** + * Wiring test for the [BUG-LOW] Past Journeys mode-icon fix + * (sq-playtest-pingpong.md, 2026-04-26). + * + * Per CLAUDE.md "Every Test Suite Needs a Wiring Test" — the unit tests + * in `lobby/__tests__/JourneyHistory.test.tsx` prove the icon renders + * when JourneyHistory is mounted directly. That's not enough: we also + * need to prove the lobby (`ConnectScreen`) actually mounts JourneyHistory + * AND that the mode badge survives the full production render path + * (provider chain, prop wiring, conditional rendering). + * + * If someone deletes `` from ConnectScreen or stops + * passing the entry's mode field through, this test fails. + */ +const GENRES: GenresResponse = { + victoria: { + name: "Victorian London", + description: "Gaslight and intrigue.", + worlds: [ + { + slug: "albion", + name: "Albion", + description: "Foggy streets.", + era: null, + setting: null, + inspirations: [], + axis_snapshot: {}, + hero_image: null, + }, + ], + }, +}; + +describe("past-journeys mode icon — lobby wiring", () => { + beforeEach(() => { + localStorage.clear(); + // ConnectScreen polls /api/sessions on mount via useSessions. + globalThis.fetch = vi.fn().mockImplementation((url: string) => { + if (typeof url === "string" && url.startsWith("/api/sessions")) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve({ sessions: [] }), + }); + } + return Promise.resolve({ ok: true, json: () => Promise.resolve({}) }); + }) as unknown as typeof fetch; + }); + + it("renders solo, multiplayer, and unknown-mode icons in the lobby past-journeys list", () => { + // Seed three entries — one of each mode flavor — exactly the way the + // production code seeds them via appendHistory after a successful start. + appendHistory({ + player_name: "SoloRider", + genre: "victoria", + world: "albion", + mode: "solo", + }); + appendHistory({ + player_name: "MPRider", + genre: "victoria", + world: "albion", + mode: "multiplayer", + }); + appendHistory({ + player_name: "LegacyRider", + genre: "victoria", + world: "albion", + // No `mode` — pre-2026-04-24 entry shape. + }); + + render( + + + , + ); + + // The lobby must surface all three rows with distinguishable mode badges. + // Locate each row by its player-name text, then assert its row contains + // a mode-tagged span. data-mode is the contract; the literal glyph is + // an implementation detail re-asserted below for the visual spec. + const soloRow = screen.getByText("SoloRider").closest("button")!; + const mpRow = screen.getByText("MPRider").closest("button")!; + const legacyRow = screen.getByText("LegacyRider").closest("button")!; + + const soloBadge = soloRow.querySelector('[data-mode="solo"]'); + const mpBadge = mpRow.querySelector('[data-mode="multiplayer"]'); + const legacyBadge = legacyRow.querySelector('[data-mode="unknown"]'); + + expect(soloBadge).not.toBeNull(); + expect(mpBadge).not.toBeNull(); + expect(legacyBadge).not.toBeNull(); + + // Visual spec — locks the chosen glyphs to the playtest report's spec + // (◈ solo, ⚑ MP) plus the legacy ◇ choice documented in modeBadge(). + expect(soloBadge!.textContent).toBe("◈"); + expect(mpBadge!.textContent).toBe("⚑"); + expect(legacyBadge!.textContent).toBe("◇"); + }); +}); diff --git a/src/screens/lobby/JourneyHistory.tsx b/src/screens/lobby/JourneyHistory.tsx index ed32e6e..c911bd1 100644 --- a/src/screens/lobby/JourneyHistory.tsx +++ b/src/screens/lobby/JourneyHistory.tsx @@ -5,6 +5,7 @@ import { formatRelativeTime, type JourneyEntry, } from "./historyStore"; +import { modeBadge } from "./modeBadge"; export interface JourneyHistoryProps { /** @@ -70,6 +71,12 @@ export function JourneyHistory({ {entries.map((entry) => { const genreName = prettyGenre(entry.genre); const worldName = prettyWorld(entry.genre, entry.world); + // Mode icon: helps Alex (slow reader, MP context) see at-a-glance + // whether a row is a solo or multiplayer save before clicking. Pre + // 2026-04-24 entries lack `mode` — render a hollow diamond rather + // than guessing, so an unknown row visually differs from a known + // solo/MP row instead of silently looking like one. + const { glyph: modeGlyph, label: modeLabel } = modeBadge(entry.mode); return (