Test mod
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { assert, assertEquals, assertThrows } from "@std/assert";
|
||||
import { check_mods } from "$/tools/check_mods.ts";
|
||||
import { create_mod } from "$/tools/new_mod.ts";
|
||||
|
||||
function temp_mods_dir() {
|
||||
const dir = Deno.makeTempDirSync({ prefix: "bworld_mods_" });
|
||||
// most mods depend on the base game
|
||||
Deno.mkdirSync(`${dir}/bworld`);
|
||||
for (const entry of ["manifest.json", "blocks", "items", "recipes"]) {
|
||||
copy(`mods/bworld/${entry}`, `${dir}/bworld/${entry}`);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
function copy(from: string, to: string) {
|
||||
if (Deno.statSync(from).isDirectory) {
|
||||
Deno.mkdirSync(to, { recursive: true });
|
||||
for (const entry of Deno.readDirSync(from)) copy(`${from}/${entry.name}`, `${to}/${entry.name}`);
|
||||
} else {
|
||||
Deno.copyFileSync(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
function write_json(path: string, value: unknown) {
|
||||
Deno.writeTextFileSync(path, JSON.stringify(value));
|
||||
}
|
||||
|
||||
Deno.test("a mod made from the template passes the checks", async () => {
|
||||
const dir = temp_mods_dir();
|
||||
create_mod("copper_tools", "Copper Tools", dir);
|
||||
const manifest = JSON.parse(Deno.readTextFileSync(`${dir}/copper_tools/manifest.json`));
|
||||
assertEquals(manifest.id, "copper_tools");
|
||||
assertEquals(manifest.name, "Copper Tools");
|
||||
assert(!Deno.readTextFileSync(`${dir}/copper_tools/blocks/example_block.json`).includes("example_mod"));
|
||||
|
||||
const report = (await check_mods(dir)).find((r) => r.id === "copper_tools")!;
|
||||
assertEquals(report.errors, []);
|
||||
assertEquals(report.warnings, []);
|
||||
Deno.removeSync(dir, { recursive: true });
|
||||
});
|
||||
|
||||
Deno.test("new-mod rejects bad and reserved ids", () => {
|
||||
const dir = temp_mods_dir();
|
||||
assertThrows(() => create_mod("Has Spaces", "x", dir));
|
||||
assertThrows(() => create_mod("bworld", "x", dir));
|
||||
assertThrows(() => create_mod("engine", "x", dir));
|
||||
create_mod("once", "x", dir);
|
||||
assertThrows(() => create_mod("once", "x", dir));
|
||||
Deno.removeSync(dir, { recursive: true });
|
||||
});
|
||||
|
||||
Deno.test("check-mods finds broken mods", async () => {
|
||||
const dir = temp_mods_dir();
|
||||
create_mod("broken", "Broken", dir);
|
||||
const mod = `${dir}/broken`;
|
||||
|
||||
// wrong namespace, a drop that doesn't exist, and a texture that doesn't exist
|
||||
write_json(`${mod}/blocks/wrong.json`, {
|
||||
format_version: 1,
|
||||
block: { id: "other:thing", textures: "broken:nope", drops: "broken:no_such_item" },
|
||||
});
|
||||
// defines a block the base game already has
|
||||
write_json(`${mod}/blocks/dupe.json`, { format_version: 1, block: { id: "broken:x", textures: "bworld:stone" } });
|
||||
write_json(`${mod}/blocks/stone_copy.json`, {
|
||||
format_version: 1,
|
||||
block: { id: "broken:x", textures: "bworld:stone" },
|
||||
});
|
||||
// not json
|
||||
Deno.writeTextFileSync(`${mod}/items/bad.json`, "{ nope");
|
||||
// uses a letter the key doesn't define
|
||||
write_json(`${mod}/recipes/bad.json`, {
|
||||
format_version: 1,
|
||||
recipe: {
|
||||
type: "shaped",
|
||||
pattern: ["AB"],
|
||||
key: { A: "bworld:stone" },
|
||||
result: { id: "bworld:stone", count: 1 },
|
||||
},
|
||||
});
|
||||
// wrong texture size
|
||||
Deno.copyFileSync("assets/sprites/ui.png", `${mod}/textures/huge.png`);
|
||||
// a script that doesn't typecheck
|
||||
Deno.writeTextFileSync(`${mod}/scripts/client.ts`, "export function setup(ctx: number) { ctx.nope(); }\n");
|
||||
// a dependency that isn't installed, and no longer depending on bworld
|
||||
const manifest = JSON.parse(Deno.readTextFileSync(`${mod}/manifest.json`));
|
||||
manifest.dependencies = [{ id: "missing_mod", version: "1.0.0" }];
|
||||
write_json(`${mod}/manifest.json`, manifest);
|
||||
|
||||
const report = (await check_mods(dir)).find((r) => r.id === "broken")!;
|
||||
const has = (list: string[], text: string) =>
|
||||
assert(list.some((e) => e.includes(text)), `expected "${text}" in\n${list.join("\n")}`);
|
||||
has(report.errors, `other:thing isn't in this mod's namespace`);
|
||||
has(report.errors, "item broken:no_such_item doesn't exist");
|
||||
has(report.errors, "block broken:x is also defined by broken, broken");
|
||||
has(report.errors, "items/bad.json: isn't valid json");
|
||||
has(report.errors, `pattern uses "B" but key doesn't define it`);
|
||||
has(report.errors, "textures/huge.png: is");
|
||||
has(report.errors, "scripts don't typecheck");
|
||||
has(report.errors, `depends on "missing_mod", which isn't installed`);
|
||||
has(report.warnings, "texture broken:nope doesn't exist");
|
||||
has(report.warnings, `uses bworld:stone but doesn't list "bworld" in dependencies`);
|
||||
Deno.removeSync(dir, { recursive: true });
|
||||
});
|
||||
Reference in New Issue
Block a user