117 lines
3.0 KiB
JavaScript
117 lines
3.0 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { YarnGame } = require('../server');
|
|
|
|
/**
|
|
* Deterministic timer double so gameplay phases can be advanced in tests
|
|
* without real waiting.
|
|
*/
|
|
function createFakeTimers() {
|
|
let nextId = 1;
|
|
const intervals = new Map();
|
|
const timeouts = new Map();
|
|
|
|
return {
|
|
intervals,
|
|
timeouts,
|
|
setInterval(fn, ms) {
|
|
const id = nextId += 1;
|
|
intervals.set(id, { fn, ms });
|
|
return id;
|
|
},
|
|
clearInterval(id) {
|
|
intervals.delete(id);
|
|
},
|
|
setTimeout(fn, ms) {
|
|
const id = nextId += 1;
|
|
timeouts.set(id, { fn, ms });
|
|
return id;
|
|
},
|
|
clearTimeout(id) {
|
|
timeouts.delete(id);
|
|
},
|
|
/** Fire every live interval `times` times (snapshotting per pass). */
|
|
runIntervals(times = 1) {
|
|
for (let pass = 0; pass < times; pass += 1) {
|
|
for (const [id, { fn }] of [...intervals.entries()]) {
|
|
if (!intervals.has(id)) continue;
|
|
fn();
|
|
}
|
|
}
|
|
},
|
|
/** Fire every pending timeout once. */
|
|
runTimeouts() {
|
|
const pending = [...timeouts.values()];
|
|
timeouts.clear();
|
|
for (const { fn } of pending) fn();
|
|
}
|
|
};
|
|
}
|
|
|
|
function tempStoriesDir() {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'yarn-test-'));
|
|
}
|
|
|
|
function makeGame(overrides = {}) {
|
|
const timers = overrides.timers || createFakeTimers();
|
|
const events = [];
|
|
const game = new YarnGame(overrides.roomName || 'test-room', {
|
|
timers,
|
|
broadcast: (event, payload) => events.push({ event, payload }),
|
|
botFactory: overrides.botFactory,
|
|
botType: overrides.botType || 'random',
|
|
storiesDir: overrides.storiesDir || tempStoriesDir()
|
|
});
|
|
|
|
const emitted = (name) => events.filter((entry) => entry.event === name);
|
|
return { game, timers, events, emitted };
|
|
}
|
|
|
|
function addPlayers(game, count) {
|
|
const ids = [];
|
|
for (let i = 0; i < count; i += 1) {
|
|
const id = `p${i + 1}`;
|
|
game.addPlayer(id, `Player ${i + 1}`, `socket-${i + 1}`);
|
|
ids.push(id);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
/** Start a game with `count` human players and no bots. */
|
|
async function startGame(game, count, settings = {}) {
|
|
const ids = addPlayers(game, count);
|
|
await game.startGame({ botCount: 0, timeLimit: 60, roundLimit: 15, ...settings });
|
|
return ids;
|
|
}
|
|
|
|
/** Submit one entry per player, in order. */
|
|
function submitAll(game, entries) {
|
|
entries.forEach(([playerId, text], index) => {
|
|
const result = game.submitEntry(playerId, text);
|
|
if (!result.ok) throw new Error(`submit failed for ${playerId}: ${result.error}`);
|
|
void index;
|
|
});
|
|
}
|
|
|
|
/** Cast the supplied votes and let the game close voting if complete. */
|
|
function voteAll(game, votes) {
|
|
for (const [playerId, entryIndex] of Object.entries(votes)) {
|
|
const result = game.submitVote(playerId, entryIndex);
|
|
if (!result.ok) throw new Error(`vote failed for ${playerId}: ${result.error}`);
|
|
}
|
|
game.checkVotesComplete();
|
|
}
|
|
|
|
module.exports = {
|
|
createFakeTimers,
|
|
tempStoriesDir,
|
|
makeGame,
|
|
addPlayers,
|
|
startGame,
|
|
submitAll,
|
|
voteAll
|
|
};
|