Stage 7 (wip): tests + UI CI

- Vitest units: board replay, placement machine, premium parity, i18n key parity, FlatBuffers codec round-trips (19 tests)
- Playwright smoke (mock transport): guest -> lobby -> board -> place tile -> preview
- ui-test.yaml workflow: check/unit/build + bundle-size budget (67.5KB gzip < 100KB) + chromium e2e
- gateway transcode tests for games.list (seat display_name), pass, hint
- backend integration test for game.ListForAccount
This commit is contained in:
Ilia Denisov
2026-06-03 00:55:38 +02:00
parent 65689b903f
commit 0284c9b83a
10 changed files with 512 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
import { describe, expect, it } from 'vitest';
import {
BLANK,
direction,
newPlacement,
place,
rackView,
recallAt,
reset,
toSubmit,
} from './placement';
const rack = ['A', 'Q', BLANK, 'N', 'I', 'W', 'E'];
describe('placement state machine', () => {
it('places a tile and marks the rack slot used', () => {
const p = place(newPlacement(rack), 0, 7, 7);
expect(p.pending).toHaveLength(1);
expect(rackView(p)[0].used).toBe(true);
expect(rackView(p)[1].used).toBe(false);
});
it('rejects reusing a slot or an occupied cell', () => {
let p = place(newPlacement(rack), 0, 7, 7);
p = place(p, 0, 7, 8); // same slot -> no-op
expect(p.pending).toHaveLength(1);
p = place(p, 1, 7, 7); // occupied cell -> no-op
expect(p.pending).toHaveLength(1);
});
it('requires a letter for a blank slot', () => {
const noLetter = place(newPlacement(rack), 2, 7, 7);
expect(noLetter.pending).toHaveLength(0);
const withLetter = place(newPlacement(rack), 2, 7, 7, 'x');
expect(withLetter.pending[0]).toMatchObject({ letter: 'X', blank: true });
});
it('recalls a tile by cell', () => {
let p = place(newPlacement(rack), 0, 7, 7);
p = recallAt(p, 7, 7);
expect(p.pending).toHaveLength(0);
expect(reset(place(p, 0, 7, 7)).pending).toHaveLength(0);
});
it('infers direction H for a row, V for a column, null for a single tile', () => {
let h = place(newPlacement(rack), 0, 7, 7);
h = place(h, 1, 7, 8);
expect(direction(h)).toBe('H');
let v = place(newPlacement(rack), 0, 7, 7);
v = place(v, 1, 8, 7);
expect(direction(v)).toBe('V');
expect(direction(place(newPlacement(rack), 0, 7, 7))).toBeNull();
});
it('builds a sorted submit payload and honours a direction override', () => {
let p = place(newPlacement(rack), 1, 7, 9);
p = place(p, 0, 7, 7);
const sub = toSubmit(p);
expect(sub?.dir).toBe('H');
expect(sub?.tiles.map((t) => t.col)).toEqual([7, 9]);
expect(toSubmit(place(newPlacement(rack), 0, 7, 7), 'V')?.dir).toBe('V');
expect(toSubmit(newPlacement(rack))).toBeNull();
});
});