import { describe, it, expect } from 'vitest'; import { activeByKind, capFor, isKindLocked } from './gamelimits'; import { GameKind, type GameView, type GameLimits } from './model'; // game builds a minimal GameView for the count: only kind, status and (optionally) hotseat matter. function game(kind: number, status: string, extra: Partial = {}): GameView { return { id: 'g', variant: 'scrabble_en', dictVersion: '', status, players: 2, toMove: 0, turnTimeoutSecs: 0, multipleWordsPerTurn: true, moveCount: 0, endReason: '', lastActivityUnix: 0, vsAi: false, unreadChat: false, unreadMessages: false, kind, seats: [], ...extra, }; } describe('activeByKind', () => { it('counts open+active games per kind, skipping finished, hotseat and untagged (kind 0)', () => { const by = activeByKind([ game(GameKind.vsAi, 'active'), game(GameKind.random, 'open'), game(GameKind.random, 'active'), game(GameKind.random, 'finished'), // finished → not counted game(GameKind.vsAi, 'active', { hotseat: true }), // local pass-and-play → not counted game(0, 'active'), // pre-existing / untagged → not counted ]); expect(by[GameKind.vsAi]).toBe(1); expect(by[GameKind.random]).toBe(2); }); }); describe('capFor', () => { const limits: GameLimits = { vsAi: 1, random: 10, friends: 0 }; it('maps each kind to its tier cap; unknown kind and absent limits are unlimited (-1)', () => { expect(capFor(limits, GameKind.vsAi)).toBe(1); expect(capFor(limits, GameKind.random)).toBe(10); expect(capFor(limits, GameKind.friends)).toBe(0); expect(capFor(limits, 0)).toBe(-1); expect(capFor(undefined, GameKind.vsAi)).toBe(-1); }); }); describe('isKindLocked', () => { const guest: GameLimits = { vsAi: 1, random: 1, friends: 0 }; const durable: GameLimits = { vsAi: 10, random: 10, friends: 10 }; it('locks a guest at the per-kind cap, not another kind', () => { const games = [game(GameKind.vsAi, 'active')]; expect(isKindLocked(games, guest, GameKind.vsAi)).toBe(true); expect(isKindLocked(games, guest, GameKind.random)).toBe(false); }); it('a cap of 0 always locks (a guest cannot start friend games)', () => { expect(isKindLocked([], guest, GameKind.friends)).toBe(true); }); it('an unlimited cap (-1) or absent limits never lock', () => { const many = [game(GameKind.random, 'active'), game(GameKind.random, 'active')]; expect(isKindLocked(many, { vsAi: -1, random: -1, friends: -1 }, GameKind.random)).toBe(false); expect(isKindLocked(many, undefined, GameKind.random)).toBe(false); }); it('a durable account stays well under its higher cap', () => { expect(isKindLocked([game(GameKind.random, 'active')], durable, GameKind.random)).toBe(false); }); it('locks only the reached kind: at the vs_ai cap, random with no games stays open', () => { const limits = { vsAi: 3, random: 2, friends: 1 }; const games = [game(GameKind.vsAi, 'active'), game(GameKind.vsAi, 'active'), game(GameKind.vsAi, 'active')]; expect(isKindLocked(games, limits, GameKind.vsAi)).toBe(true); expect(isKindLocked(games, limits, GameKind.random)).toBe(false); expect(isKindLocked(games, limits, GameKind.friends)).toBe(false); }); });