57 lines
3.0 KiB
JavaScript
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)));
|
|
});
|