// Shared fixtures for the test suite. A small deterministic map is used // throughout so tests are fast and their results stable. import { GameState } from "../../shared/game_state.js"; import { parseKey } from "../../shared/hex.js"; import { MAP_CONFIG, CIVILISATIONS } from "../../shared/data.js"; import { startServer } from "../../server/server.js"; // Boots the real server on an ephemeral port for an integration test, then // guarantees it is closed afterwards. export async function withServer(fn) { const server = startServer({ port: 0, bind: "127.0.0.1", log: () => {} }); const address = await server.listen; try { await fn(address.port, server); } finally { server.close(); } } export const SEED = 12345; export const SEA = "Sea"; export function smallConfig(overrides = {}) { return { ...MAP_CONFIG, mapSize: { x: 24, y: 16 }, minCapitalLandmass: 1, minCapitalDistance: 2, citiesPerCiv: 2, citySpacing: 2, ...overrides, }; } export function smallState(civIds = ["france", "britain"], seed = SEED, overrides = {}) { const state = new GameState(); state.mapConfig = smallConfig(overrides); const civs = civIds.map((id) => CIVILISATIONS.find((c) => c.id === id)); state.configure(civs, seed); return state; } // A state on the full default map, so its coordinates line up with the terrain // the browser view regenerates from MAP_CONFIG. export function defaultState(civIds = ["france", "britain"], seed = SEED) { const state = new GameState(); const civs = civIds.map((id) => CIVILISATIONS.find((c) => c.id === id)); state.configure(civs, seed); return state; } // The six render layers of the world map, as jQuery objects. export function mapLayers($) { return { terrain: $("#layer-terrain"), roads: $("#layer-roads"), borders: $("#layer-borders"), fog: $("#layer-fog"), highlight: $("#layer-highlight"), paths: $("#layer-paths"), targets: $("#layer-targets"), entities: $("#layer-entities"), labels: $("#layer-labels"), }; } export function adjacentLand(state, unit) { for (const neighbour of state.topology.neighbours(unit.coords.x, unit.coords.y)) { if (state._isLand(neighbour)) return neighbour; } return null; } export function seaTile(state) { for (const k in state.tiles) { if (state.tiles[k].terrainClass === SEA) return parseKey(k); } return null; } export function cityOf(state, civ) { return state.cities.find((city) => city.civ === civ) || null; } // Gives a city a level of a building directly, so a test that trains a unit // gated behind a building does not have to walk the construction queue first. export function grantBuilding(state, city, id, level = 1) { const index = state.protoBuildings.findIndex((building) => building.id === id); if (index < 0) return -1; city.buildings[index] = level; state._gdpPerCapitaCache.clear(); state._touchModifiers(); return index; } export function unitsOf(state, civ) { return state.units.filter((unit) => unit.civ === civ).length; } export function snapshotUnit(state, unitId, viewer = 0) { return (state.snapshot(viewer).units || []).find((u) => u.id === unitId) || null; } export function gdpBuilding(overrides = {}) { return { id: "test_market", name: "Test market", baseCost: 100, costAlpha: 2.0, refundFraction: 0.5, effects: [{ stat: "gdp", baseValue: 0.5, exponent: 1.0 }], ...overrides, }; }