6fbab5417f
Two follow-up nits on the F8-04b sidebar: 1. The bare-`lobby` resolver (lobby-screen.svelte) redirected to `games-recruitment` unconditionally on mount. With games already in the player's roster the sidebar then highlighted the wrong sub-page. The resolver now awaits the lobby fan-out + account fetch, then hands off to the same `firstVisibleGamesScreen` helper the sidebar uses — so a fresh entry with games lands on `active-past`, the canonical-order fallback stays `recruitment`. 2. `games-invitations` was unconditionally visible in the sidebar. Now it follows the `active-past` rule: hidden until the pending-invites list reports >=1. The lobby shell's auto-kick effect treats it symmetrically — accepting / declining the last invite moves the player to the next visible sub-page once the fan-out has resolved. Acceptance order in games-invitations-screen.acceptInvite was also swapped to setMyGames-before-removeInvitation: both mutations land in the same microtask, so the new auto-kick sees the freshly added game in `myGames` when invitations drop to zero and routes the player to `active-past` instead of bouncing through `recruitment`. The visibility predicates and canonical order live in the new `src/lib/lobby-nav.ts` pure helper, shared between the sidebar and the resolver so they cannot disagree. Unit tests cover every combination of (hasMyGames, hasInvitations, isPaidOrDev). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
// 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> = {}): 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",
|
|
);
|
|
});
|
|
});
|