feat(offline): hotseat creation roster + host-participate flow
NewGame offline 'with friends' now builds a local pass-and-play game: - lib/roster.ts: keep-last-valid name + roster->seats (unit-tested). - NewGame.svelte: master host-PIN gate (rows disabled until set), 'are you playing too?' prompt seating the host at row 0, 2-4 player rows with inline name validation + optional per-seat PIN, add/remove (remove behind the master PIN), variant + multiple-words, Start. - PinPad: verification via a verify(pin) callback (UI-held lock at creation, source-held in-game) instead of a lock prop. - i18n: hotseat.* (en + ru); the offline mode selector is now shown offline.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildSeats, commitName, rosterReady, type RosterRow } from './roster';
|
||||
|
||||
describe('commitName (keep-last-valid)', () => {
|
||||
it('commits a valid, trimmed name', () => {
|
||||
expect(commitName('Ann', '')).toBe('Ann');
|
||||
expect(commitName(' Ann ', '')).toBe('Ann');
|
||||
});
|
||||
|
||||
it('keeps the previous valid name when the input goes invalid', () => {
|
||||
expect(commitName('', 'Ann')).toBe('Ann');
|
||||
expect(commitName(' ', 'Ann')).toBe('Ann');
|
||||
expect(commitName('a'.repeat(33), 'Ann')).toBe('Ann'); // over the 32-rune cap
|
||||
expect(commitName('_bad', 'Ann')).toBe('Ann'); // leading separator
|
||||
});
|
||||
|
||||
it('replaces the committed name with a newer valid one', () => {
|
||||
expect(commitName('Bob', 'Ann')).toBe('Bob');
|
||||
});
|
||||
});
|
||||
|
||||
describe('rosterReady', () => {
|
||||
const row = (committed: string): RosterRow => ({ name: committed, committed });
|
||||
|
||||
it('needs 2-4 rows, each with a committed name', () => {
|
||||
expect(rosterReady([row('A'), row('B')])).toBe(true);
|
||||
expect(rosterReady([row('A'), row('B'), row('C'), row('D')])).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects fewer than 2, more than 4, or an empty row', () => {
|
||||
expect(rosterReady([row('A')])).toBe(false);
|
||||
expect(rosterReady([row('A'), row('B'), row('C'), row('D'), row('E')])).toBe(false);
|
||||
expect(rosterReady([row('A'), row('')])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildSeats', () => {
|
||||
it('maps committed rows to account-less human seats, carrying the PIN', () => {
|
||||
const lock = { hash: 'h', salt: 's' };
|
||||
const seats = buildSeats([
|
||||
{ name: 'Ann', committed: 'Ann', pin: lock },
|
||||
{ name: 'x', committed: 'Bob' },
|
||||
]);
|
||||
expect(seats).toEqual([
|
||||
{ kind: 'human', name: 'Ann', pin: lock },
|
||||
{ kind: 'human', name: 'Bob', pin: undefined },
|
||||
]);
|
||||
expect(seats.every((s) => !('accountId' in s) || s.accountId === undefined)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user