// Pure-state coverage for the lobby `games` submenu visibility rules. // The same helper drives the sidebar's auto-expanded sub-item and the // bare-`lobby` resolver's redirect target; if they ever disagree the // player lands on the wrong sub-page on entry. import { describe, expect, test } from "vitest"; import { firstVisibleGamesScreen, visibleGamesSubScreens, type LobbyNavState, } from "../src/lib/lobby-nav"; function state(overrides: Partial = {}): LobbyNavState { return { hasMyGames: false, hasInvitations: false, isPaidOrDev: false, ...overrides, }; } describe("visibleGamesSubScreens", () => { test("free-tier player with nothing — only recruitment is visible", () => { expect(visibleGamesSubScreens(state())).toEqual(["games-recruitment"]); }); test("player with games — active-past leads recruitment", () => { expect(visibleGamesSubScreens(state({ hasMyGames: true }))).toEqual([ "games-active-past", "games-recruitment", ]); }); test("player with pending invites — invitations appears after recruitment", () => { expect(visibleGamesSubScreens(state({ hasInvitations: true }))).toEqual([ "games-recruitment", "games-invitations", ]); }); test("paid / DEV tier — private-games tails the list", () => { expect(visibleGamesSubScreens(state({ isPaidOrDev: true }))).toEqual([ "games-recruitment", "games-private-games", ]); }); test("all four flags on — full canonical order", () => { expect( visibleGamesSubScreens( state({ hasMyGames: true, hasInvitations: true, isPaidOrDev: true, }), ), ).toEqual([ "games-active-past", "games-recruitment", "games-invitations", "games-private-games", ]); }); test("recruitment is always present regardless of input", () => { const variants: LobbyNavState[] = [ state(), state({ hasMyGames: true }), state({ hasInvitations: true }), state({ isPaidOrDev: true }), state({ hasMyGames: true, hasInvitations: true, isPaidOrDev: true }), ]; for (const v of variants) { expect(visibleGamesSubScreens(v)).toContain("games-recruitment"); } }); }); describe("firstVisibleGamesScreen", () => { test("free-tier nothing → recruitment", () => { expect(firstVisibleGamesScreen(state())).toBe("games-recruitment"); }); test("player with games → active-past wins", () => { expect(firstVisibleGamesScreen(state({ hasMyGames: true }))).toBe( "games-active-past", ); }); test("invitee-only (no games yet) → recruitment still wins over invitations by canonical order", () => { // Pending invites do NOT promote `invitations` above `recruitment`; // the canonical order places `recruitment` second only to // `active-past`. This guards against accidentally re-ordering the // sub-items in a way that surprises invitee-only sessions. expect(firstVisibleGamesScreen(state({ hasInvitations: true }))).toBe( "games-recruitment", ); }); test("games + invites → active-past still wins", () => { expect( firstVisibleGamesScreen( state({ hasMyGames: true, hasInvitations: true }), ), ).toBe("games-active-past"); }); test("paid tier with no games → recruitment, not private-games", () => { expect(firstVisibleGamesScreen(state({ isPaidOrDev: true }))).toBe( "games-recruitment", ); }); });