aether-shards/test/units/Explorer.test.js

241 lines
7.4 KiB
JavaScript
Raw Permalink Normal View History

import { expect } from "@esm-bundle/chai";
import { Explorer } from "../../src/units/Explorer.js";
// Mock Class Definitions
const CLASS_VANGUARD = {
id: "CLASS_VANGUARD",
base_stats: { health: 100, attack: 10, speed: 5 },
growth_rates: { health: 10, attack: 1 },
};
const CLASS_TINKER = {
id: "CLASS_TINKER",
base_stats: { health: 80, attack: 8, speed: 7 },
growth_rates: { health: 5, attack: 2 },
};
describe("Unit: Explorer Class Logic", () => {
it("CoA 1: Should initialize with base stats from definition", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
expect(hero.baseStats.health).to.equal(100);
expect(hero.baseStats.attack).to.equal(10);
expect(hero.classMastery["CLASS_VANGUARD"]).to.exist;
expect(hero.classMastery["CLASS_VANGUARD"].level).to.equal(1);
});
it("CoA 2: Should calculate stats based on Level Growth", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
// Manually level up to 3
hero.classMastery["CLASS_VANGUARD"].level = 3;
hero.recalculateBaseStats(CLASS_VANGUARD);
// Level 3 means 2 level-ups.
// Health: 100 + (10 * 2) = 120
// Attack: 10 + (1 * 2) = 12
expect(hero.baseStats.health).to.equal(120);
expect(hero.baseStats.attack).to.equal(12);
});
it("CoA 3: changeClass should switch stats and persist old progress", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
// Level up Vanguard
hero.classMastery["CLASS_VANGUARD"].level = 5;
hero.recalculateBaseStats(CLASS_VANGUARD);
expect(hero.baseStats.health).to.equal(140); // 100 + 40
// Switch to Tinker (New Job)
hero.changeClass("CLASS_TINKER", CLASS_TINKER);
// Should have Level 1 Tinker Stats
expect(hero.activeClassId).to.equal("CLASS_TINKER");
expect(hero.baseStats.health).to.equal(80); // Base Tinker
// Verify Vanguard history is saved
expect(hero.classMastery["CLASS_VANGUARD"].level).to.equal(5);
});
it("CoA 4: Switching BACK to old class should restore high stats", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
hero.classMastery["CLASS_VANGUARD"].level = 5;
// Switch Away
hero.changeClass("CLASS_TINKER", CLASS_TINKER);
// Switch Back
hero.changeClass("CLASS_VANGUARD", CLASS_VANGUARD);
// Should be back to Level 5 Stats
expect(hero.baseStats.health).to.equal(140);
});
describe("recalculateStats with Skill Tree", () => {
it("should apply skill tree stat boosts to maxHealth", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
hero.recalculateBaseStats(CLASS_VANGUARD);
hero.maxHealth = hero.baseStats.health;
hero.currentHealth = hero.maxHealth;
// Initial health should be 100
expect(hero.maxHealth).to.equal(100);
// Create skill tree with health boost
const treeDef = {
nodes: {
ROOT: {
id: "ROOT",
type: "STAT_BOOST",
data: { stat: "health", value: 10 },
},
},
};
// Unlock the node
hero.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
// Recalculate stats with tree definition
hero.recalculateStats(null, treeDef);
// Health should be increased by 10
expect(hero.maxHealth).to.equal(110);
});
it("should apply multiple skill tree stat boosts", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
hero.recalculateBaseStats(CLASS_VANGUARD);
hero.maxHealth = hero.baseStats.health;
hero.currentHealth = hero.maxHealth;
const treeDef = {
nodes: {
ROOT: {
id: "ROOT",
type: "STAT_BOOST",
data: { stat: "health", value: 10 },
},
NODE_2: {
id: "NODE_2",
type: "STAT_BOOST",
data: { stat: "health", value: 5 },
},
},
};
// Unlock both nodes
hero.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT", "NODE_2"];
hero.recalculateStats(null, treeDef);
// Health should be increased by 15 (10 + 5)
expect(hero.maxHealth).to.equal(115);
});
it("should only apply stat boosts from unlocked nodes", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
hero.recalculateBaseStats(CLASS_VANGUARD);
hero.maxHealth = hero.baseStats.health;
hero.currentHealth = hero.maxHealth;
const treeDef = {
nodes: {
ROOT: {
id: "ROOT",
type: "STAT_BOOST",
data: { stat: "health", value: 10 },
},
LOCKED_NODE: {
id: "LOCKED_NODE",
type: "STAT_BOOST",
data: { stat: "health", value: 20 },
},
},
};
// Only unlock ROOT, not LOCKED_NODE
hero.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
hero.recalculateStats(null, treeDef);
// Should only get boost from ROOT (10), not LOCKED_NODE (20)
expect(hero.maxHealth).to.equal(110);
});
it("should apply stat boosts to non-health stats", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
hero.recalculateBaseStats(CLASS_VANGUARD);
const treeDef = {
nodes: {
ATTACK_BOOST: {
id: "ATTACK_BOOST",
type: "STAT_BOOST",
data: { stat: "attack", value: 5 },
},
},
};
hero.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ATTACK_BOOST"];
// Note: recalculateStats doesn't return stats, but we can verify it was called
// The actual stat application is tested through CharacterSheet UI tests
hero.recalculateStats(null, treeDef);
// Verify the method completed without error
expect(hero.maxHealth).to.exist;
});
it("should update currentHealth proportionally when maxHealth changes", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
hero.recalculateBaseStats(CLASS_VANGUARD);
hero.maxHealth = 100;
hero.currentHealth = 50; // 50% health
const treeDef = {
nodes: {
ROOT: {
id: "ROOT",
type: "STAT_BOOST",
data: { stat: "health", value: 20 },
},
},
};
hero.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
hero.recalculateStats(null, treeDef);
// maxHealth should be 120 (100 + 20)
expect(hero.maxHealth).to.equal(120);
// currentHealth should be proportionally adjusted (50% of 120 = 60)
expect(hero.currentHealth).to.equal(60);
});
it("should handle treeDef with no unlocked nodes", () => {
const hero = new Explorer("p1", "Hero", "CLASS_VANGUARD", CLASS_VANGUARD);
hero.recalculateBaseStats(CLASS_VANGUARD);
hero.maxHealth = hero.baseStats.health;
hero.currentHealth = hero.maxHealth;
const treeDef = {
nodes: {
ROOT: {
id: "ROOT",
type: "STAT_BOOST",
data: { stat: "health", value: 10 },
},
},
};
// No unlocked nodes
hero.classMastery["CLASS_VANGUARD"].unlockedNodes = [];
hero.recalculateStats(null, treeDef);
// Health should remain unchanged
expect(hero.maxHealth).to.equal(100);
});
});
});