Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
5.8 kB · 179 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180/** * The grouped preset picker, and the MegaMek camo directory it sits next to. * * Two things here are easy to break silently. The picker marks a tile chosen * by comparing its label to the palette's name, so two presets sharing a name * light up together and neither is wrong enough to notice. And the camo files * are generated from a MegaMek release by a script nobody runs on a normal * day, so a half-finished run, a rename upstream or a stray delete leaves an * index naming files that are not there — which fails as a broken thumbnail in * a dialog, long after the commit that caused it. * * Run with `npm test`. */import { test } from "node:test";import assert from "node:assert/strict";import { access, mkdtemp, readFile } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { execFile } from "node:child_process";import { promisify } from "node:util";import { fileURLToPath } from "node:url";
import { CURATED } from "./megamek-camo.data.mjs";
const run = promisify(execFile);const web = fileURLToPath(new URL("../", import.meta.url));const CAMO_DIR = join(web, "public/megamek-camo");
// palette.ts is TypeScript importing TypeScript, so it is bundled with the// site's own bundler rather than run through a TS runner. Same shape as// camo-record.test.mjs; the output goes to a temp directory.const dir = await mkdtemp(join(tmpdir(), "camo-presets-"));const bundle = join(dir, "fixture.mjs");await run( join(web, "node_modules/.bin/rolldown"), [ join(web, "scripts/camo-presets.fixture.ts"), "--format", "esm", "--platform", "node", "--file", bundle, ], { cwd: web },);
const { PALETTE_GROUPS, PRESETS } = await import(bundle);
test("every preset is in a group the picker draws a chip for", () => { for (const preset of PRESETS) { assert.ok( preset.group, `${preset.name} has no group, so no chip shows it and it is unreachable`, ); assert.ok( PALETTE_GROUPS.includes(preset.group), `${preset.name} is in "${preset.group}", which is not in PALETTE_GROUPS`, ); } for (const group of PALETTE_GROUPS) { assert.ok( PRESETS.some((p) => p.group === group), `the "${group}" chip would open an empty grid`, ); }});
test("preset names are unique", () => { const seen = new Set(); for (const preset of PRESETS) { assert.ok( !seen.has(preset.name), `two presets are called "${preset.name}"; the picker marks the chosen ` + "tile by name, so both would look chosen", ); seen.add(preset.name); }});
test("every preset is four colours of whole 0-255 channels", () => { for (const preset of PRESETS) { assert.equal( preset.colors.length, 4, `${preset.name} has ${preset.colors.length} colours; the tile draws four bands`, ); for (const color of preset.colors) { assert.equal( color.length, 3, `${preset.name} has a colour that is not RGB`, ); for (const channel of color) { assert.ok( Number.isInteger(channel) && channel >= 0 && channel <= 255, `${preset.name} has channel ${channel}, which the record encoder rejects`, ); } } }});
test("flags are Pride palettes and nothing else is", () => { for (const preset of PRESETS) { assert.equal( preset.flag === true, preset.group === "Pride", `${preset.name} disagrees with itself: flag is ${preset.flag} and the ` + `group is "${preset.group}". randomCamo excludes flags, so a faction ` + "marked as one would silently drop out of the roll", ); }});
test("the roll still excludes flags", async () => { const random = await readFile(join(web, "src/camo/random.ts"), "utf8"); assert.match( random, /PRESETS\.filter\(\(p\) => !p\.flag\)/, "a flag is something a person picks on purpose, not something the page " + "hands them on load", );});
test("the camo index names files that are on disk", async () => { const index = JSON.parse( await readFile(join(CAMO_DIR, "index.json"), "utf8"), ); assert.ok(index.release, "the index does not say which release it came from");
for (const category of index.categories) { assert.ok(category.camos.length > 0, `"${category.name}" is empty`); for (const camo of category.camos) { await assert.doesNotReject( access(join(CAMO_DIR, camo.file)), `${camo.file} is in the index and not in public/megamek-camo/`, ); } }});
test("the index carries every curated entry", async () => { const index = JSON.parse( await readFile(join(CAMO_DIR, "index.json"), "utf8"), ); for (const group of CURATED) { const category = index.categories.find((c) => c.name === group.name); assert.ok(category, `"${group.name}" is curated and not in the index`); for (const entry of group.entries) { assert.ok( category.camos.some( (c) => c.name === entry.name && c.src === entry.src, ), `${entry.name} is curated and not in the index; re-run ` + "`MM_HOME=... npm run megamek-camo`", ); } }});
test("every faction the script derives a palette for has one", () => { for (const group of CURATED.filter((g) => g.palettes)) { for (const entry of group.entries) { const preset = PRESETS.find((p) => p.name === entry.name); assert.ok( preset, `${entry.name} has camo art but no palette; the script prints the ` + "block, and pasting it into palette.ts is a manual step", ); assert.equal( preset.group, group.name, `${entry.name} is filed under "${preset.group}" and its art under ` + `"${group.name}"`, ); } }});