Refactor org platform navigation and content flows
All checks were successful
Build Org Website / build (push) Successful in 38s
All checks were successful
Build Org Website / build (push) Successful in 38s
This commit is contained in:
@@ -1,70 +1,91 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const data = require("../assets/scripts/pages/archive-world-data.js");
|
||||
const state = require("../assets/scripts/pages/archive-world-state.js");
|
||||
|
||||
test("fresh state applies defaults", () => {
|
||||
assert.deepEqual(state.fresh(" Ada ", "moss"), {
|
||||
version: 1,
|
||||
name: "Ada",
|
||||
palette: "moss",
|
||||
discovered: [],
|
||||
complete: false,
|
||||
position: { x: 640, y: 830 },
|
||||
soundEnabled: false
|
||||
});
|
||||
test("fresh v2 state applies safe defaults", () => {
|
||||
const fresh = state.fresh(" Ada ", "moss");
|
||||
assert.equal(fresh.version, 2);
|
||||
assert.equal(fresh.name, "Ada");
|
||||
assert.equal(fresh.palette, "moss");
|
||||
assert.equal(fresh.position.area, "town");
|
||||
assert.equal(fresh.health, 100);
|
||||
assert.equal(fresh.settings.soundEnabled, false);
|
||||
assert.deepEqual(fresh.sigils, []);
|
||||
assert.equal(fresh.inventory.find((item) => item.id === "ink_vial").quantity, 2);
|
||||
});
|
||||
|
||||
test("names are trimmed, collapsed, and limited to twenty characters", () => {
|
||||
test("names and palettes are validated", () => {
|
||||
assert.equal(state.validateName(" Archive Guest "), "Archive Guest");
|
||||
assert.equal(state.validateName(""), null);
|
||||
assert.equal(state.validateName("x".repeat(21)), null);
|
||||
});
|
||||
|
||||
test("only declared palettes are accepted", () => {
|
||||
assert.equal(state.validatePalette("berry"), "berry");
|
||||
assert.equal(state.validatePalette("ultraviolet"), null);
|
||||
});
|
||||
|
||||
test("discoveries are deduplicated and completion requires all landmarks", () => {
|
||||
let current = state.fresh("Ada", "brass");
|
||||
current = state.discover(current, "gate");
|
||||
current = state.discover(current, "gate");
|
||||
assert.deepEqual(current.discovered, ["gate"]);
|
||||
assert.equal(current.complete, false);
|
||||
|
||||
for (const id of state.LANDMARK_IDS) current = state.discover(current, id);
|
||||
assert.equal(current.complete, true);
|
||||
});
|
||||
|
||||
test("normalization filters unknown discoveries and clamps position", () => {
|
||||
const candidate = {
|
||||
test("v1 data migrates identity, discoveries, position and sound", () => {
|
||||
const migrated = state.migrateV1({
|
||||
version: 1,
|
||||
name: "Zaine",
|
||||
palette: "brass",
|
||||
discovered: ["gate", "gate", "missing"],
|
||||
complete: true,
|
||||
position: { x: -20, y: 5000 },
|
||||
soundEnabled: true
|
||||
};
|
||||
assert.deepEqual(state.normalize(candidate), {
|
||||
version: 1,
|
||||
name: "Zaine",
|
||||
palette: "brass",
|
||||
discovered: ["gate"],
|
||||
name: "Mira",
|
||||
palette: "berry",
|
||||
discovered: ["gate", "gate", "museum", "missing"],
|
||||
complete: false,
|
||||
position: { x: 36, y: 924 },
|
||||
position: { x: 700, y: 850 },
|
||||
soundEnabled: true
|
||||
});
|
||||
assert.equal(migrated.version, 2);
|
||||
assert.deepEqual(migrated.discovered, ["gate", "museum"]);
|
||||
assert.equal(migrated.position.area, "town");
|
||||
assert.equal(migrated.position.x, 700);
|
||||
assert.equal(migrated.settings.soundEnabled, true);
|
||||
});
|
||||
|
||||
test("parse rejects malformed, unsupported, and incomplete saves", () => {
|
||||
test("normalization deduplicates and filters restored collections", () => {
|
||||
const candidate = state.fresh("Zaine", "brass");
|
||||
candidate.discovered = ["gate", "gate", "missing"];
|
||||
candidate.sigils = ["garden", "garden", "bad"];
|
||||
candidate.inventory.push({ id: "ink_vial", quantity: 99 }, { id: "invalid", quantity: 2 });
|
||||
candidate.defeatedBosses = ["sentinel", "sentinel", "fake"];
|
||||
candidate.solvedPuzzles = ["gate", "gate", "fake"];
|
||||
const restored = state.normalize(candidate);
|
||||
assert.deepEqual(restored.discovered, ["gate"]);
|
||||
assert.deepEqual(restored.sigils, ["garden"]);
|
||||
assert.equal(restored.inventory.find((item) => item.id === "ink_vial").quantity, data.ITEMS.ink_vial.stack);
|
||||
assert.deepEqual(restored.defeatedBosses, ["sentinel"]);
|
||||
assert.deepEqual(restored.solvedPuzzles, ["gate"]);
|
||||
});
|
||||
|
||||
test("quest restoration accepts only known statuses and bounded counters", () => {
|
||||
const candidate = state.fresh("Ada", "brass");
|
||||
candidate.quests.gate = { status: "active", step: 200, count: -5 };
|
||||
candidate.quests.library = { status: "nonsense", step: 2, count: 3 };
|
||||
const restored = state.normalize(candidate);
|
||||
assert.deepEqual(restored.quests.gate, { status: "active", step: 20, count: 0 });
|
||||
assert.deepEqual(restored.quests.library, { status: "locked", step: 2, count: 3 });
|
||||
});
|
||||
|
||||
test("position, health, settings and unsupported schemas recover safely", () => {
|
||||
const candidate = state.fresh("Ada", "brass");
|
||||
candidate.position = { area: "missing", x: -100, y: 9000, facing: "downward" };
|
||||
candidate.health = 999;
|
||||
candidate.settings = { soundEnabled: true, ambience: 9, music: -2, effects: "bad", assist: true };
|
||||
const restored = state.normalize(candidate);
|
||||
assert.equal(restored.position.area, "town");
|
||||
assert.equal(restored.position.x, 24);
|
||||
assert.equal(restored.position.y, 1062);
|
||||
assert.equal(restored.position.facing, "north");
|
||||
assert.equal(restored.health, restored.maxHealth);
|
||||
assert.equal(restored.settings.ambience, 1);
|
||||
assert.equal(restored.settings.music, 0);
|
||||
assert.equal(restored.settings.effects, 0.65);
|
||||
assert.equal(state.parse("{"), null);
|
||||
assert.equal(state.parse(JSON.stringify({ version: 2 })), null);
|
||||
assert.equal(state.parse(JSON.stringify(state.fresh("", "brass"))), null);
|
||||
assert.equal(state.parse(JSON.stringify({ version: 99 })), null);
|
||||
});
|
||||
|
||||
test("a valid save round-trips and reset creates an empty state", () => {
|
||||
const saved = state.discover(state.fresh("Mira", "berry"), "garden");
|
||||
assert.deepEqual(state.parse(JSON.stringify(saved)), saved);
|
||||
test("level thresholds and reset-ready fresh state are deterministic", () => {
|
||||
assert.equal(state.levelForXp(0), 1);
|
||||
assert.equal(state.levelForXp(80), 2);
|
||||
assert.equal(state.levelForXp(620), 5);
|
||||
assert.deepEqual(state.fresh("Mira", "berry").discovered, []);
|
||||
assert.equal(state.fresh("Mira", "berry").quests.gate.status, "locked");
|
||||
});
|
||||
|
||||
85
tests/archive-world-systems.test.cjs
Normal file
85
tests/archive-world-systems.test.cjs
Normal file
@@ -0,0 +1,85 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const data = require("../assets/scripts/pages/archive-world-data.js");
|
||||
const state = require("../assets/scripts/pages/archive-world-state.js");
|
||||
const systems = require("../assets/scripts/pages/archive-world-systems.js");
|
||||
|
||||
test("movement normalization prevents faster diagonals", () => {
|
||||
const straight = systems.normalizedVector(1, 0, 175);
|
||||
const diagonal = systems.normalizedVector(1, 1, 175);
|
||||
assert.equal(Math.round(Math.hypot(straight.x, straight.y)), 175);
|
||||
assert.equal(Math.round(Math.hypot(diagonal.x, diagonal.y)), 175);
|
||||
});
|
||||
|
||||
test("landmark discoveries deduplicate and complete at eight", () => {
|
||||
let current = state.fresh("Ada", "brass");
|
||||
current = systems.discover(current, "gate");
|
||||
current = systems.discover(current, "gate");
|
||||
assert.deepEqual(current.discovered, ["gate"]);
|
||||
data.LANDMARK_IDS.forEach((id) => { current = systems.discover(current, id); });
|
||||
assert.equal(current.complete, true);
|
||||
});
|
||||
|
||||
test("quest progression and completion grant each reward once", () => {
|
||||
let current = state.fresh("Ada", "brass");
|
||||
current = systems.startQuest(current, "gate");
|
||||
assert.equal(current.quests.gate.status, "active");
|
||||
current = systems.completeQuest(current, "gate");
|
||||
const xp = current.xp;
|
||||
assert.equal(current.quests.gate.status, "complete");
|
||||
assert.deepEqual(current.sigils, ["gate"]);
|
||||
assert.equal(current.inventory.some((item) => item.id === "lantern"), true);
|
||||
current = systems.completeQuest(current, "gate");
|
||||
assert.equal(current.xp, xp);
|
||||
assert.deepEqual(current.sigils, ["gate"]);
|
||||
});
|
||||
|
||||
test("inventory collection caps stacks and consumables heal", () => {
|
||||
let current = state.fresh("Ada", "brass");
|
||||
current = systems.addItem(current, "ink_vial", 30);
|
||||
assert.equal(systems.itemQuantity(current, "ink_vial"), 9);
|
||||
current.health = 30;
|
||||
const used = systems.consumeItem(current, "ink_vial");
|
||||
assert.equal(used.used, true);
|
||||
assert.equal(used.state.health, 65);
|
||||
assert.equal(systems.itemQuantity(used.state, "ink_vial"), 8);
|
||||
});
|
||||
|
||||
test("damage respects invulnerability and assist, defeat preserves progress", () => {
|
||||
let current = state.fresh("Ada", "brass");
|
||||
current.sigils = ["gate"];
|
||||
const assisted = state.normalize({ ...current, settings: { ...current.settings, assist: true } });
|
||||
const first = systems.takeDamage(assisted, 20, 2000, 0);
|
||||
assert.equal(first.state.health, 90);
|
||||
const ignored = systems.takeDamage(first.state, 20, 2200, 2000);
|
||||
assert.equal(ignored.hit, false);
|
||||
const defeated = systems.takeDamage({ ...first.state, health: 5, settings: { ...first.state.settings, assist: false } }, 20, 5000, 0);
|
||||
assert.equal(defeated.defeated, true);
|
||||
const respawned = systems.respawn(defeated.state);
|
||||
assert.deepEqual(respawned.sigils, ["gate"]);
|
||||
assert.equal(respawned.health >= 50, true);
|
||||
});
|
||||
|
||||
test("XP levels, boss completion and sigil story stage restore correctly", () => {
|
||||
let current = state.fresh("Ada", "brass");
|
||||
current = systems.grantXp(current, 200);
|
||||
assert.equal(current.level, 3);
|
||||
assert.equal(current.maxHealth, 120);
|
||||
current = systems.recordBoss(current, "sentinel");
|
||||
assert.equal(current.defeatedBosses.includes("sentinel"), true);
|
||||
assert.equal(current.quests.workshop.status, "complete");
|
||||
data.LANDMARK_IDS.forEach((id) => {
|
||||
if (!current.sigils.includes(id)) current = systems.completeQuest(current, id);
|
||||
});
|
||||
assert.equal(current.sigils.length, 8);
|
||||
assert.equal(current.story.stage, "vault");
|
||||
current = systems.recordBoss(current, "redactor");
|
||||
assert.equal(current.story.stage, "postgame");
|
||||
});
|
||||
|
||||
test("invalid collision positions resolve to a named safe spawn", () => {
|
||||
assert.equal(systems.isSafe("town", 200, 150), false);
|
||||
const safe = systems.nearestSafeSpawn("town", 200, 150);
|
||||
assert.equal(systems.isSafe(safe.area, safe.x, safe.y), true);
|
||||
assert.ok(["gate", "square", "library", "study", "inn", "workshop", "playroom", "museum", "garden"].includes(safe.spawn));
|
||||
});
|
||||
Reference in New Issue
Block a user