e40adfb0c7
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m56s
Address review of the active-game limit lock: - Live-event GameViews (opponent_moved, match_found, game_started, ...) did not carry game_kind, so a lobby patch from an event zeroed a game's kind and the client's per-kind count under-counted — a capped kind read as free (a disabled start instead of the lock, and a wrong per-kind result). Thread Kind through notify.GameSummary → the event GameView. - New Game screen refreshes the lobby games on mount so the per-kind count reflects the current set, not a stale cached snapshot. - The guest funnel's login button routes to the profile screen (the account controls), not settings. - Copy: the guest prompt is "sign in to use all the game's features"; the durable notice is "finish your active games to start a new one". Regression tests: the notify opponent-moved payload carries kind; the client lock locks only the reached kind (vs_ai at cap leaves random open).
41 lines
2.0 KiB
TypeScript
41 lines
2.0 KiB
TypeScript
import { expect, test } from './fixtures';
|
|
|
|
// The active-game limit lock on the New Game screen. When a start's kind is at the caller's per-kind
|
|
// cap (Profile.game_limits), the start button shows a 🔒 and opens a funnel modal instead of a game;
|
|
// when the cap lifts (the profile refetch a guest→durable upgrade would trigger), the lock clears and
|
|
// the button starts a game again. Driven by the mock's __mock.setGameLimits seam (no backend). The
|
|
// native Telegram popup path is verified on the contour; here the cross-platform in-app modal shows
|
|
// (the mock profile is a durable account, so it is the "finish a current game first" notice).
|
|
|
|
type Limits = { vsAi: number; random: number; friends: number };
|
|
function setLimits(l: Limits) {
|
|
(window as unknown as { __mock: { setGameLimits(l: Limits): void } }).__mock.setGameLimits(l);
|
|
}
|
|
|
|
test('new game: a start locks at its per-kind cap and the funnel modal opens', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: /guest/i }).click();
|
|
|
|
// The New Game button always opens the screen now — the per-kind lock lives on the start button.
|
|
await page.getByRole('button', { name: /🎲/ }).click();
|
|
|
|
const start = page.getByRole('button', { name: /start game/i });
|
|
await expect(start).not.toContainText('🔒');
|
|
|
|
// Cap every kind: the AI start (the default opponent) locks.
|
|
await page.evaluate(setLimits, { vsAi: 0, random: 0, friends: 0 });
|
|
await expect(start).toContainText('🔒');
|
|
|
|
// Tapping the locked start opens the modal instead of a game, and does not navigate away.
|
|
await start.click();
|
|
const notice = page.getByText(/reached the limit/i);
|
|
await expect(notice).toBeVisible();
|
|
await expect(page).toHaveURL(/\/new$/);
|
|
await page.getByRole('button', { name: /^ok$/i }).click();
|
|
await expect(notice).toHaveCount(0);
|
|
|
|
// Lift the cap (the same profile-refetch path a registration takes): the lock clears.
|
|
await page.evaluate(setLimits, { vsAi: 10, random: 10, friends: 10 });
|
|
await expect(start).not.toContainText('🔒');
|
|
});
|