Files
healer-man/scripts/combat-effects/package-wow-original.test.mjs
2026-09-11 18:49:33 -04:00

57 lines
3.0 KiB
JavaScript

import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import { auditWowOriginalManifest, normalizeWowEffectPath, packageWowOriginalEffects } from "./package-wow-original.mjs";
import { decodeWotlkM2Animation, decodeWotlkM2Mesh } from "./wotlk-m2-mesh.mjs";
test("normalizes client model paths without allowing traversal", () => {
assert.equal(normalizeWowEffectPath("Spells\\Fireball.mdx", ".m2"), "spells/fireball.m2");
for (const name of ["../secret.m2", "spells/../secret.m2", "/spells/test.m2", "C:/secret.m2"]) {
assert.throws(() => normalizeWowEffectPath(name), /Invalid WoW source path/);
}
});
test("all ten WoW classes package every referenced visual phase", () => {
const manifest = JSON.parse(fs.readFileSync(new URL("../../public/assets/game/combat-effects/manifest.json", import.meta.url)));
const coverage = manifest.coverage.originalVisuals;
assert.equal(coverage.scope, "wow335-all-classes");
assert.equal(Object.keys(coverage.classes).length, 10);
assert.equal(coverage.packagedModels, coverage.models);
assert.deepEqual(coverage.failures, []);
for (const [classId, entry] of Object.entries(coverage.classes)) {
assert.ok(entry.withOriginalVisuals > 0, classId);
assert.equal(entry.packagedPhaseAssets, entry.referencedPhaseAssets, classId);
}
assert.deepEqual(auditWowOriginalManifest(manifest).missing, []);
});
test("reports unavailable source models without falsely marking them ready", () => {
const original = { entries: [
{ key: "wow335:133", source: "wow335", spellId: 133, phases: { impact: [{ model: "Spells/Missing.mdx", effectGraphUrl: "/old.json" }] } },
{ key: "runewaker:1", source: "runewaker", spellId: 1 },
] };
const result = packageWowOriginalEffects(original, {
source: { read: () => null }, catalog: { classes: { mage: [{ ranks: [{ spellId: 133 }] }] } },
});
assert.equal(result.report.failures.length, 1);
assert.equal(result.manifest.entries[0].phases.impact[0].effectGraphUrl, undefined);
assert.deepEqual(result.manifest.entries[1], original.entries[1]);
assert.equal(original.entries[0].phases.impact[0].effectGraphUrl, "/old.json");
});
const sourceRoot = path.resolve("../LadiksMPQEditor/common-2.mpq/spells");
test("decodes Death Knight dual textures with a single layer transparency track", {
skip: !fs.existsSync(path.join(sourceRoot, "deathknight_bloodstrike.m2")),
}, () => {
const m2 = fs.readFileSync(path.join(sourceRoot, "deathknight_bloodstrike.m2"));
const skin = fs.readFileSync(path.join(sourceRoot, "deathknight_bloodstrike00.skin"));
const mesh = decodeWotlkM2Mesh(m2, skin);
assert.equal(mesh.skin.textureUnits[0].textureIndices.length, 2);
assert.equal(mesh.skin.textureUnits[0].textureWeightIndices.length, 1);
const animation = decodeWotlkM2Animation(m2);
assert.ok(animation.bones.length > 0);
assert.ok(animation.bones.flatMap((b) => b.rotation.sequences.flatMap((s) => s.values))
.every((q) => q.every(Number.isFinite)));
});