import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { Rooms } from './rooms'; import { Store } from './store'; let dir: string; beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'rooms-')); }); afterEach(() => { rmSync(dir, { recursive: true, force: true }); }); /** A table of three with Ann, who opened it, and Bo seated, the duel begun. */ function table() { const rooms = new Rooms(new Store(dir)); const host = rooms.create('Ann', 3); const guest = rooms.join(host.room, 'Bo'); rooms.begin(host.room, host.token); return { rooms, room: host.room, ann: host.token, bo: guest.token }; } describe('rooms', () => { it("gives a seat a new token at the keeper's word: the old one opens nothing, and a restart keeps the change", () => { const { rooms, room, bo } = table(); const phrase = rooms.transfer(room, bo).phrase; const { seat, token } = rooms.reseat(room, 'B'); expect(seat.name).toBe('Bo'); expect(rooms.seatOf(room, token).id).toBe('B'); expect(() => rooms.seatOf(room, bo)).toThrow(/opens no seat/); // A phrase minted with the old token carries nothing now. expect(() => rooms.claim(phrase)).toThrow(/unknown or has expired/); const reopened = new Rooms(new Store(dir)).get(room.id); expect(rooms.seatOf(reopened, token).id).toBe('B'); expect(() => rooms.seatOf(reopened, bo)).toThrow(/opens no seat/); }); it('re-seats only a wizard, and says when each last acted', () => { const { rooms, room, ann } = table(); expect(() => rooms.reseat(room, 'Z')).toThrow(/No wizard sits/); rooms.submit(room, ann, { left: 'P', right: '-', casts: [] }, 0); const seats = rooms.seatsForKeeper(room); expect(seats.map((s) => [s.id, s.name])).toEqual([['A', 'Ann'], ['B', 'Bo']]); expect(seats[0].lastActed).toBeGreaterThan(0); expect(seats[1].lastActed).toBeNull(); }); });