Add comprehensive tests for the InventoryManager and InventoryContainer to validate item management functionalities. Implement integration tests for the CharacterSheet component, ensuring proper interaction with the inventory system. Update the Explorer class to support new inventory features and maintain backward compatibility. Refactor related components for improved clarity and performance.
612 lines
21 KiB
JavaScript
612 lines
21 KiB
JavaScript
import { expect } from "@esm-bundle/chai";
|
|
import { SkillTreeUI } from "../../src/ui/components/SkillTreeUI.js";
|
|
import { Explorer } from "../../src/units/Explorer.js";
|
|
import vanguardDef from "../../src/assets/data/classes/vanguard.json" with {
|
|
type: "json",
|
|
};
|
|
|
|
describe("UI: SkillTreeUI", () => {
|
|
let element;
|
|
let container;
|
|
let testUnit;
|
|
let mockTreeDef;
|
|
|
|
beforeEach(() => {
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
element = document.createElement("skill-tree-ui");
|
|
container.appendChild(element);
|
|
|
|
// Create a test Explorer unit
|
|
testUnit = new Explorer("test-unit-1", "Test Vanguard", "CLASS_VANGUARD", vanguardDef);
|
|
testUnit.classMastery["CLASS_VANGUARD"] = {
|
|
level: 5,
|
|
xp: 250,
|
|
skillPoints: 3,
|
|
unlockedNodes: [],
|
|
};
|
|
testUnit.recalculateBaseStats(vanguardDef);
|
|
|
|
// Create mock tree definition
|
|
mockTreeDef = {
|
|
id: "TREE_TEST",
|
|
nodes: {
|
|
ROOT: {
|
|
id: "ROOT",
|
|
tier: 1,
|
|
type: "STAT_BOOST",
|
|
children: ["NODE_1", "NODE_2"],
|
|
data: { stat: "health", value: 10 },
|
|
req: 1,
|
|
cost: 1,
|
|
},
|
|
NODE_1: {
|
|
id: "NODE_1",
|
|
tier: 2,
|
|
type: "ACTIVE_SKILL",
|
|
children: ["NODE_3"],
|
|
data: { name: "Shield Bash", id: "SKILL_SHIELD_BASH" },
|
|
req: 2,
|
|
cost: 1,
|
|
},
|
|
NODE_2: {
|
|
id: "NODE_2",
|
|
tier: 2,
|
|
type: "STAT_BOOST",
|
|
children: [],
|
|
data: { stat: "defense", value: 5 },
|
|
req: 2,
|
|
cost: 1,
|
|
},
|
|
NODE_3: {
|
|
id: "NODE_3",
|
|
tier: 3,
|
|
type: "PASSIVE_ABILITY",
|
|
children: [],
|
|
data: { name: "Iron Skin", id: "PASSIVE_IRON_SKIN" },
|
|
req: 3,
|
|
cost: 2,
|
|
},
|
|
},
|
|
};
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (container.parentNode) {
|
|
container.parentNode.removeChild(container);
|
|
}
|
|
});
|
|
|
|
// Helper to wait for LitElement update
|
|
async function waitForUpdate() {
|
|
await element.updateComplete;
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
}
|
|
|
|
// Helper to query shadow DOM
|
|
function queryShadow(selector) {
|
|
return element.shadowRoot?.querySelector(selector);
|
|
}
|
|
|
|
function queryShadowAll(selector) {
|
|
return element.shadowRoot?.querySelectorAll(selector) || [];
|
|
}
|
|
|
|
describe("CoA 1: Dynamic Rendering", () => {
|
|
it("should render tree with variable tier depths", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const tierRows = queryShadowAll(".tier-row");
|
|
expect(tierRows.length).to.be.greaterThan(0);
|
|
|
|
// Should have nodes from different tiers
|
|
const nodes = queryShadowAll(".voxel-node");
|
|
expect(nodes.length).to.equal(4); // ROOT, NODE_1, NODE_2, NODE_3
|
|
});
|
|
|
|
it("should update node states immediately when unit changes", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Initially, ROOT should be available (level 5 >= req 1)
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
expect(rootNode).to.exist;
|
|
expect(rootNode.classList.contains("available")).to.be.true;
|
|
|
|
// Unlock ROOT
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
element.unit = { ...testUnit }; // Trigger update
|
|
await waitForUpdate();
|
|
|
|
const updatedRootNode = queryShadow('[data-node-id="ROOT"]');
|
|
expect(updatedRootNode.classList.contains("unlocked")).to.be.true;
|
|
});
|
|
|
|
it("should handle tier 1 to tier 5 nodes", async () => {
|
|
const multiTierTree = {
|
|
id: "TREE_MULTI",
|
|
nodes: {
|
|
T1: { id: "T1", tier: 1, type: "STAT_BOOST", children: ["T2"], req: 1, cost: 1 },
|
|
T2: { id: "T2", tier: 2, type: "STAT_BOOST", children: ["T3"], req: 2, cost: 1 },
|
|
T3: { id: "T3", tier: 3, type: "STAT_BOOST", children: ["T4"], req: 3, cost: 1 },
|
|
T4: { id: "T4", tier: 4, type: "STAT_BOOST", children: ["T5"], req: 4, cost: 1 },
|
|
T5: { id: "T5", tier: 5, type: "STAT_BOOST", children: [], req: 5, cost: 1 },
|
|
},
|
|
};
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = multiTierTree;
|
|
await waitForUpdate();
|
|
|
|
const tierRows = queryShadowAll(".tier-row");
|
|
expect(tierRows.length).to.equal(5);
|
|
});
|
|
});
|
|
|
|
describe("CoA 2: Validation Feedback", () => {
|
|
it("should show inspector with disabled button for locked node", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Click on NODE_3 which requires NODE_1 to be unlocked first
|
|
const node3 = queryShadow('[data-node-id="NODE_3"]');
|
|
node3.click();
|
|
await waitForUpdate();
|
|
|
|
const inspector = queryShadow(".inspector");
|
|
expect(inspector.classList.contains("visible")).to.be.true;
|
|
|
|
const unlockButton = queryShadow(".unlock-button");
|
|
expect(unlockButton.hasAttribute("disabled")).to.be.true;
|
|
|
|
const errorMessage = queryShadow(".error-message");
|
|
expect(errorMessage).to.exist;
|
|
expect(errorMessage.textContent).to.include("Requires");
|
|
});
|
|
|
|
it("should show 'Insufficient Points' for available node with 0 SP", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 0;
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Click on NODE_1 which is available but costs 1 SP
|
|
const node1 = queryShadow('[data-node-id="NODE_1"]');
|
|
node1.click();
|
|
await waitForUpdate();
|
|
|
|
const errorMessage = queryShadow(".error-message");
|
|
expect(errorMessage).to.exist;
|
|
expect(errorMessage.textContent).to.include("Insufficient Points");
|
|
|
|
const unlockButton = queryShadow(".unlock-button");
|
|
expect(unlockButton.hasAttribute("disabled")).to.be.true;
|
|
});
|
|
|
|
it("should enable unlock button for available node with sufficient SP", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 3;
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Click on NODE_1 which is available
|
|
const node1 = queryShadow('[data-node-id="NODE_1"]');
|
|
node1.click();
|
|
await waitForUpdate();
|
|
|
|
const unlockButton = queryShadow(".unlock-button");
|
|
expect(unlockButton.hasAttribute("disabled")).to.be.false;
|
|
expect(unlockButton.textContent).to.include("Unlock");
|
|
});
|
|
|
|
it("should show 'Unlocked' state for already unlocked nodes", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
rootNode.click();
|
|
await waitForUpdate();
|
|
|
|
const unlockButton = queryShadow(".unlock-button");
|
|
expect(unlockButton.textContent).to.include("Unlocked");
|
|
expect(unlockButton.hasAttribute("disabled")).to.be.true;
|
|
});
|
|
});
|
|
|
|
describe("CoA 3: Responsive Lines", () => {
|
|
it("should draw connection lines between nodes", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Trigger connection update
|
|
element._updateConnections();
|
|
await waitForUpdate();
|
|
|
|
const svg = queryShadow(".connections-overlay svg");
|
|
expect(svg).to.exist;
|
|
|
|
const paths = queryShadowAll(".connections-overlay svg path");
|
|
expect(paths.length).to.be.greaterThan(0);
|
|
});
|
|
|
|
it("should update connection lines on resize", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Trigger initial connection update
|
|
element._updateConnections();
|
|
await waitForUpdate();
|
|
|
|
const initialPaths = queryShadowAll(".connections-overlay svg path");
|
|
const initialCount = initialPaths.length;
|
|
expect(initialCount).to.be.greaterThan(0);
|
|
|
|
// Simulate resize by changing container size
|
|
const container = queryShadow(".tree-container");
|
|
container.style.width = "200px";
|
|
container.style.height = "200px";
|
|
|
|
// Wait for ResizeObserver to trigger
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
await waitForUpdate();
|
|
|
|
// Paths should still exist (may have been redrawn)
|
|
const pathsAfterResize = queryShadowAll(".connections-overlay svg path");
|
|
expect(pathsAfterResize.length).to.equal(initialCount);
|
|
});
|
|
|
|
it("should style connection lines based on child node status", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Trigger connection update
|
|
element._updateConnections();
|
|
await waitForUpdate();
|
|
|
|
// ROOT -> NODE_1 connection
|
|
const paths = queryShadowAll(".connections-overlay svg path");
|
|
expect(paths.length).to.be.greaterThan(0);
|
|
|
|
// Verify paths exist (they should have status classes applied by _updateConnections)
|
|
// Note: Paths may not have classes if nodes aren't rendered yet, which is acceptable
|
|
expect(paths.length).to.be.greaterThan(0);
|
|
|
|
// Unlock ROOT and NODE_1 by directly modifying the unit's classMastery
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT", "NODE_1"];
|
|
// Trigger update by setting the unit property again (Lit will detect the change)
|
|
element.unit = { ...testUnit };
|
|
// Also increment updateTrigger to force re-render
|
|
element.updateTrigger = (element.updateTrigger || 0) + 1;
|
|
await waitForUpdate();
|
|
|
|
// Trigger connection update after state change
|
|
element._updateConnections();
|
|
await waitForUpdate();
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Connection to NODE_1 should now be unlocked style
|
|
// (Connection from ROOT to NODE_1, where NODE_1 is now unlocked)
|
|
const updatedPaths = queryShadowAll(".connections-overlay svg path");
|
|
expect(updatedPaths.length).to.be.greaterThan(0);
|
|
|
|
// Verify NODE_1 exists and has been updated
|
|
const node1 = queryShadow('[data-node-id="NODE_1"]');
|
|
expect(node1).to.exist;
|
|
// Node should have a status class (unlocked, available, or locked)
|
|
const node1HasStatusClass = node1.classList.contains("unlocked") ||
|
|
node1.classList.contains("available") ||
|
|
node1.classList.contains("locked");
|
|
expect(node1HasStatusClass).to.be.true;
|
|
|
|
// Connection styling is based on child status
|
|
// Verify that paths have status classes and were updated
|
|
// Paths have class "connection-line" plus status class
|
|
const allPathClasses = Array.from(updatedPaths).map((p) => Array.from(p.classList));
|
|
const pathHasStatusClass = allPathClasses.some((classes) =>
|
|
classes.includes("connection-line") &&
|
|
(classes.includes("locked") || classes.includes("available") || classes.includes("unlocked"))
|
|
);
|
|
expect(pathHasStatusClass).to.be.true;
|
|
});
|
|
});
|
|
|
|
describe("CoA 4: Scroll Position", () => {
|
|
it("should scroll to highest tier with available node on open", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT", "NODE_1"];
|
|
testUnit.classMastery["CLASS_VANGUARD"].level = 5;
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// NODE_3 should be available (tier 3, parent NODE_1 is unlocked, level 5 >= req 3)
|
|
// The scroll should center on NODE_3
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
|
|
const node3 = queryShadow('[data-node-id="NODE_3"]');
|
|
expect(node3).to.exist;
|
|
// Note: scrollIntoView behavior is hard to test in headless, but we verify the node exists
|
|
});
|
|
|
|
it("should handle case where no nodes are available", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].level = 1;
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = [];
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Should not crash, tree should still render
|
|
const treeContainer = queryShadow(".tree-container");
|
|
expect(treeContainer).to.exist;
|
|
});
|
|
});
|
|
|
|
describe("Node Status Calculation", () => {
|
|
it("should mark node as UNLOCKED if in unlockedNodes", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
expect(rootNode.classList.contains("unlocked")).to.be.true;
|
|
});
|
|
|
|
it("should mark node as AVAILABLE if parent unlocked and level requirement met", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
testUnit.classMastery["CLASS_VANGUARD"].level = 2;
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const node1 = queryShadow('[data-node-id="NODE_1"]');
|
|
expect(node1.classList.contains("available")).to.be.true;
|
|
});
|
|
|
|
it("should mark node as LOCKED if parent not unlocked", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = [];
|
|
testUnit.classMastery["CLASS_VANGUARD"].level = 5;
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const node1 = queryShadow('[data-node-id="NODE_1"]');
|
|
expect(node1.classList.contains("locked")).to.be.true;
|
|
});
|
|
|
|
it("should mark node as LOCKED if level requirement not met", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
testUnit.classMastery["CLASS_VANGUARD"].level = 1; // Below NODE_1 req of 2
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const node1 = queryShadow('[data-node-id="NODE_1"]');
|
|
expect(node1.classList.contains("locked")).to.be.true;
|
|
});
|
|
});
|
|
|
|
describe("Inspector Footer", () => {
|
|
it("should show inspector when node is clicked", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
rootNode.click();
|
|
await waitForUpdate();
|
|
|
|
const inspector = queryShadow(".inspector");
|
|
expect(inspector.classList.contains("visible")).to.be.true;
|
|
});
|
|
|
|
it("should hide inspector when close button is clicked", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
rootNode.click();
|
|
await waitForUpdate();
|
|
|
|
const closeButton = queryShadow(".inspector-close");
|
|
closeButton.click();
|
|
await waitForUpdate();
|
|
|
|
const inspector = queryShadow(".inspector");
|
|
expect(inspector.classList.contains("visible")).to.be.false;
|
|
});
|
|
|
|
it("should display node information in inspector", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const node1 = queryShadow('[data-node-id="NODE_1"]');
|
|
node1.click();
|
|
await waitForUpdate();
|
|
|
|
const title = queryShadow(".inspector-title");
|
|
expect(title.textContent).to.include("Shield Bash");
|
|
});
|
|
|
|
it("should dispatch unlock-request event when unlock button is clicked", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 3;
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
let unlockEventFired = false;
|
|
let unlockEventDetail = null;
|
|
element.addEventListener("unlock-request", (e) => {
|
|
unlockEventFired = true;
|
|
unlockEventDetail = e.detail;
|
|
});
|
|
|
|
const node1 = queryShadow('[data-node-id="NODE_1"]');
|
|
node1.click();
|
|
await waitForUpdate();
|
|
|
|
const unlockButton = queryShadow(".unlock-button");
|
|
unlockButton.click();
|
|
await waitForUpdate();
|
|
|
|
expect(unlockEventFired).to.be.true;
|
|
expect(unlockEventDetail.nodeId).to.equal("NODE_1");
|
|
expect(unlockEventDetail.cost).to.equal(1);
|
|
});
|
|
|
|
it("should update node display when updateTrigger changes", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Initially ROOT should be available
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
expect(rootNode.classList.contains("available")).to.be.true;
|
|
|
|
// Unlock the node in the unit
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
|
|
// Increment updateTrigger to force re-render
|
|
element.updateTrigger = (element.updateTrigger || 0) + 1;
|
|
await waitForUpdate();
|
|
|
|
// Now ROOT should show as unlocked
|
|
const updatedRootNode = queryShadow('[data-node-id="ROOT"]');
|
|
expect(updatedRootNode.classList.contains("unlocked")).to.be.true;
|
|
});
|
|
|
|
it("should update inspector footer when updateTrigger changes after unlock", async () => {
|
|
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 2;
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
// Click on ROOT node
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
rootNode.click();
|
|
await waitForUpdate();
|
|
|
|
// Initially should show "Unlock" button
|
|
const unlockButton = queryShadow(".unlock-button");
|
|
expect(unlockButton.textContent).to.include("Unlock");
|
|
|
|
// Simulate unlock by updating unit and incrementing updateTrigger
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 1;
|
|
element.updateTrigger = (element.updateTrigger || 0) + 1;
|
|
await waitForUpdate();
|
|
|
|
// Now should show "Unlocked" button
|
|
const updatedUnlockButton = queryShadow(".unlock-button");
|
|
expect(updatedUnlockButton.textContent).to.include("Unlocked");
|
|
expect(updatedUnlockButton.hasAttribute("disabled")).to.be.true;
|
|
});
|
|
});
|
|
|
|
describe("Voxel Node Rendering", () => {
|
|
it("should render voxel cubes with 6 faces", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
const faces = rootNode.querySelectorAll(".cube-face");
|
|
expect(faces.length).to.equal(6);
|
|
});
|
|
|
|
it("should apply correct CSS classes based on node status", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const rootNode = queryShadow('[data-node-id="ROOT"]');
|
|
expect(rootNode.classList.contains("available")).to.be.true;
|
|
|
|
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
|
|
element.unit = { ...testUnit };
|
|
await waitForUpdate();
|
|
|
|
const updatedRootNode = queryShadow('[data-node-id="ROOT"]');
|
|
expect(updatedRootNode.classList.contains("unlocked")).to.be.true;
|
|
});
|
|
|
|
it("should display appropriate icons for different node types", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const statNode = queryShadow('[data-node-id="ROOT"] .node-icon');
|
|
expect(statNode.textContent).to.include("📈");
|
|
|
|
const skillNode = queryShadow('[data-node-id="NODE_1"] .node-icon');
|
|
expect(skillNode.textContent).to.include("⚔️");
|
|
});
|
|
});
|
|
|
|
describe("Edge Cases", () => {
|
|
it("should handle missing unit gracefully", async () => {
|
|
element.unit = null;
|
|
element.treeDef = mockTreeDef;
|
|
await waitForUpdate();
|
|
|
|
const placeholder = queryShadow(".placeholder");
|
|
expect(placeholder).to.exist;
|
|
expect(placeholder.textContent).to.include("No unit selected");
|
|
});
|
|
|
|
it("should handle missing tree definition gracefully", async () => {
|
|
element.unit = testUnit;
|
|
element.treeDef = null;
|
|
await waitForUpdate();
|
|
|
|
// Should fall back to mock tree or show placeholder
|
|
const treeContainer = queryShadow(".tree-container");
|
|
expect(treeContainer).to.exist;
|
|
});
|
|
|
|
it("should handle nodes without children", async () => {
|
|
const treeWithLeafNodes = {
|
|
id: "TREE_LEAF",
|
|
nodes: {
|
|
LEAF: { id: "LEAF", tier: 1, type: "STAT_BOOST", children: [], req: 1, cost: 1 },
|
|
},
|
|
};
|
|
|
|
element.unit = testUnit;
|
|
element.treeDef = treeWithLeafNodes;
|
|
await waitForUpdate();
|
|
|
|
// Trigger connection update (should not crash with no children)
|
|
element._updateConnections();
|
|
await waitForUpdate();
|
|
|
|
const svg = queryShadow(".connections-overlay svg");
|
|
expect(svg).to.exist;
|
|
// Should not crash when drawing connections
|
|
});
|
|
});
|
|
});
|
|
|