f5c2404123
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 32s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m7s
Complete the client-side draft feature on top of the shipped backend
foundation (the game_drafts store/service):
- FB: DraftRequest{game_id,json} + DraftView{json} (a draft get reuses
GameActionRequest); regenerated committed Go + TS bindings.
- Backend REST: GET/PUT /games/:id/draft, a draftDTO
(rack_order/board_tiles) mapped to game.Draft.
- Gateway: draft.get/draft.save transcode forwarding the composition
JSON verbatim (json.RawMessage both ways -- no double-encode).
- UI: debounced save of the rack order + board tiles and restore on
load (lib/draft.ts), plus #5 -- tiles may be arranged on the
opponent's turn (placement relaxed; the preview and Make-move stay
your-turn-only, so an off-turn draft is position-only).
Tests: backend handler validation, gateway pass-through round-trip, UI
draft/codec units, and a draft-restore e2e.
46 lines
1.8 KiB
TypeScript
46 lines
1.8 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { liveDraftTiles, parseDraft, serializeDraft, validRackOrder } from './draft';
|
|
import type { PendingTile } from './placement';
|
|
|
|
describe('draft', () => {
|
|
it('round-trips the rack order and board tiles', () => {
|
|
const pending: PendingTile[] = [
|
|
{ rackIndex: 2, row: 7, col: 7, letter: 'Q', blank: false },
|
|
{ rackIndex: 0, row: 7, col: 8, letter: 'I', blank: true },
|
|
];
|
|
const parsed = parseDraft(serializeDraft([3, 0, 1, 2], pending));
|
|
expect(parsed).not.toBeNull();
|
|
expect(parsed!.rackOrder).toEqual([3, 0, 1, 2]);
|
|
expect(parsed!.tiles).toEqual([
|
|
{ row: 7, col: 7, letter: 'Q', blank: false },
|
|
{ row: 7, col: 8, letter: 'I', blank: true },
|
|
]);
|
|
});
|
|
|
|
it('parses an empty or malformed draft as null', () => {
|
|
expect(parseDraft('')).toBeNull();
|
|
expect(parseDraft('not json')).toBeNull();
|
|
});
|
|
|
|
it('parses a draft with no rack order or tiles', () => {
|
|
expect(parseDraft(JSON.stringify({ rack_order: '', board_tiles: [] }))).toEqual({ rackOrder: [], tiles: [] });
|
|
});
|
|
|
|
it('accepts a valid rack permutation and rejects a stale one', () => {
|
|
expect(validRackOrder([2, 0, 1], 3)).toEqual([2, 0, 1]);
|
|
expect(validRackOrder([0, 1], 3)).toBeNull(); // wrong length (the rack changed)
|
|
expect(validRackOrder([0, 0, 1], 3)).toBeNull(); // a duplicate, not a permutation
|
|
expect(validRackOrder([0, 1, 3], 3)).toBeNull(); // an index out of range
|
|
});
|
|
|
|
it('drops draft tiles whose cell is now occupied', () => {
|
|
const tiles = [
|
|
{ row: 7, col: 7, letter: 'A', blank: false },
|
|
{ row: 7, col: 8, letter: 'B', blank: false },
|
|
];
|
|
expect(liveDraftTiles(tiles, (r, c) => r === 7 && c === 7)).toEqual([
|
|
{ row: 7, col: 8, letter: 'B', blank: false },
|
|
]);
|
|
});
|
|
});
|