112 lines
3.9 KiB
JavaScript
112 lines
3.9 KiB
JavaScript
// The transforms have to leave a document the roster parser still understands,
|
|
// and the whole point is that what comes out the far end reads like the official
|
|
// card. These tests go through the real parser rather than inspecting the DOM.
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { Create40kRoster11th } from "../roster40k-11th.js";
|
|
import { applyTransforms } from "./index.js";
|
|
|
|
const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "__fixtures__");
|
|
const GAME_TYPE = "Warhammer 40,000 11th Edition";
|
|
|
|
/** Parse a fixture, optionally transforming it, and build the roster. */
|
|
function build(name, { transform }) {
|
|
const xml = readFileSync(join(FIXTURES, `${name}.ros`), "utf8");
|
|
const doc = new DOMParser().parseFromString(xml, "text/xml");
|
|
if (transform) applyTransforms(doc);
|
|
return Create40kRoster11th(doc, GAME_TYPE);
|
|
}
|
|
|
|
const units = (roster) => roster.forces.flatMap((force) => force.units);
|
|
const unitNamed = (roster, name) =>
|
|
units(roster).find((unit) => unit.name.includes(name));
|
|
|
|
describe("the transformed roster still parses", () => {
|
|
it.each(["bellisarius-cawl", "datasmith", "rangers", "skatros"])(
|
|
"builds a roster from %s",
|
|
(example) => {
|
|
const roster = build(example, { transform: true });
|
|
expect(roster).toBeTruthy();
|
|
expect(roster.forces.length).toBeGreaterThan(0);
|
|
expect(units(roster).length).toBeGreaterThan(0);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("merging duplicates", () => {
|
|
it("turns two Skatros cards into one carrying both weapons", () => {
|
|
const before = build("skatros", { transform: false });
|
|
const after = build("skatros", { transform: true });
|
|
|
|
const skatrosBefore = units(before).filter((unit) =>
|
|
unit.name.includes("Skatros"),
|
|
);
|
|
const skatrosAfter = units(after).filter((unit) =>
|
|
unit.name.includes("Skatros"),
|
|
);
|
|
expect(skatrosBefore.length).toBe(2);
|
|
expect(skatrosAfter.length).toBe(1);
|
|
|
|
// The single card now lists the weapon each copy was taken for.
|
|
const weapons = [
|
|
...skatrosAfter[0].rangedWeapons,
|
|
...skatrosAfter[0].meleeWeapons,
|
|
]
|
|
.map((weapon) => weapon.name)
|
|
.join(" | ");
|
|
expect(weapons).toMatch(/jezzail/i);
|
|
expect(weapons).toMatch(/arquebus/i);
|
|
});
|
|
|
|
it("brings an option's own ability along with the model that carries it", () => {
|
|
const after = build("rangers", { transform: true });
|
|
const rangers = unitNamed(after, "Ranger");
|
|
|
|
const abilityNames = Object.values(rangers.abilities).flatMap((group) =>
|
|
Array.from(group.keys()),
|
|
);
|
|
expect(abilityNames.join(" | ")).toMatch(/data-tether/i);
|
|
});
|
|
});
|
|
|
|
describe("stripping Leader/Support", () => {
|
|
// The Datasmith is a Support character, not a Leader; the transform strips
|
|
// both, and this is the example that has one.
|
|
it("leaves no attachment ability on the card", () => {
|
|
const before = build("datasmith", { transform: false });
|
|
const after = build("datasmith", { transform: true });
|
|
|
|
const abilitiesOf = (roster) =>
|
|
units(roster)
|
|
.flatMap((unit) => Object.values(unit.abilities))
|
|
.flatMap((group) => Array.from(group.keys()))
|
|
.join(" | ");
|
|
|
|
expect(abilitiesOf(before)).toMatch(/^Support \|/);
|
|
expect(abilitiesOf(after)).not.toMatch(/Leader|Support/);
|
|
});
|
|
});
|
|
|
|
describe("splitting choice abilities", () => {
|
|
it("gives each Canticle its own row under one titled group", () => {
|
|
const after = build("bellisarius-cawl", { transform: true });
|
|
const cawl = unitNamed(after, "Cawl");
|
|
|
|
const group = cawl.abilities["CANTICLES OF THE OMNISSIAH"];
|
|
expect(group).toBeTruthy();
|
|
expect(group.size).toBeGreaterThan(1);
|
|
|
|
// The option text keeps GW's ^^keyword^^ / **emphasis** markers for the
|
|
// renderer to format; only the row titles are plain text, since those
|
|
// render as-is.
|
|
expect([...group.values()].join("\n")).toMatch(/\^\^/);
|
|
for (const name of group.keys()) {
|
|
expect(name).not.toMatch(/\^\^|\*\*/);
|
|
}
|
|
});
|
|
});
|