- presets/templates/: 3 light era templates (ww2/present/near-future) + index.json manifest (id, name, year, eraDescription, suggestedPowers, tensions). - server/templates.ts: createTemplateStore (mirror of createPresetStore). - shared/scenario.ts: dependency-free contract for the dynamic scenario path — SuggestedPower / EraTemplate / TemplateMeta / WorldNation / ScenarioEvent / WorldDraft / Checkpoint types, relationFromScore, validateWorldDraft (pure, defensive, never throws), worldDraftToGameState (region-key territory via the injected expand resolver, regionStats seeded, clean turn-1 state). - tests: server/templates.test.ts (manifest order + file shape), server/scenario-shared.test.ts (15 cases: validation/clamp/dedup/null + transform). Fixes #52
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { createTemplateStore } from './templates';
|
|
|
|
// The project root is the parent of server/ (mirrors how server/index.ts and
|
|
// server/regions.test.ts locate it via import.meta.url).
|
|
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
const templatesDir = path.join(root, 'presets', 'templates');
|
|
|
|
const store = createTemplateStore(templatesDir);
|
|
|
|
/** A well-formed ISO_A3 country code: exactly three uppercase letters. */
|
|
function isIsoA3(code: string): boolean {
|
|
return /^[A-Z]{3}$/.test(code);
|
|
}
|
|
|
|
describe('era templates store (v0.31.0, #52)', () => {
|
|
it('the manifest lists exactly the three era templates', () => {
|
|
const meta = store.listMeta();
|
|
expect(meta).toHaveLength(3);
|
|
const ids = meta.map((m) => m.id);
|
|
expect(ids).toEqual(['ww2', 'present', 'near-future']);
|
|
for (const m of meta) {
|
|
expect(m.id).toBeTruthy();
|
|
expect(m.name).toBeTruthy();
|
|
expect(typeof m.year).toBe('number');
|
|
expect(m.eraDescription).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
it('each template file loads and is well-formed', () => {
|
|
for (const m of store.listMeta()) {
|
|
const t = store.get(m.id);
|
|
expect(t, `template ${m.id} should load`).not.toBeNull();
|
|
const tpl = t as NonNullable<typeof t>;
|
|
expect(tpl.id).toBe(m.id);
|
|
expect(tpl.name).toBe(m.name);
|
|
expect(tpl.year).toBe(m.year);
|
|
expect(tpl.eraDescription).toBeTruthy();
|
|
// suggested powers: non-empty, each a named power with an ISO_A3 core region + note.
|
|
expect(tpl.suggestedPowers.length).toBeGreaterThan(0);
|
|
for (const p of tpl.suggestedPowers) {
|
|
expect(p.name).toBeTruthy();
|
|
expect(isIsoA3(p.region)).toBe(true);
|
|
expect(p.note).toBeTruthy();
|
|
}
|
|
// tensions: a non-empty list of non-empty strings.
|
|
expect(tpl.tensions.length).toBeGreaterThan(0);
|
|
for (const s of tpl.tensions) expect(typeof s).toBe('string');
|
|
for (const s of tpl.tensions) expect(s.trim()).not.toBe('');
|
|
}
|
|
});
|
|
|
|
it('get returns null for an unknown id', () => {
|
|
expect(store.get('does-not-exist')).toBeNull();
|
|
expect(store.get('')).toBeNull();
|
|
});
|
|
});
|