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.
433 lines
14 KiB
JavaScript
433 lines
14 KiB
JavaScript
import { expect } from "@esm-bundle/chai";
|
|
import { DeploymentHUD } from "../../src/ui/deployment-hud.js";
|
|
|
|
describe("UI: DeploymentHUD", () => {
|
|
let element;
|
|
let container;
|
|
|
|
beforeEach(() => {
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
element = document.createElement("deployment-hud");
|
|
container.appendChild(element);
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (container.parentNode) {
|
|
container.parentNode.removeChild(container);
|
|
}
|
|
});
|
|
|
|
// Helper to wait for LitElement update
|
|
async function waitForUpdate() {
|
|
await element.updateComplete;
|
|
// Give a small delay for DOM updates
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
}
|
|
|
|
// Helper to query shadow DOM
|
|
function queryShadow(selector) {
|
|
return element.shadowRoot?.querySelector(selector);
|
|
}
|
|
|
|
function queryShadowAll(selector) {
|
|
return element.shadowRoot?.querySelectorAll(selector) || [];
|
|
}
|
|
|
|
describe("CoA 1: Basic Rendering", () => {
|
|
it("should render deployment HUD with squad units", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Vanguard", classId: "CLASS_VANGUARD", icon: "🛡️" },
|
|
{ id: "u2", name: "Weaver", classId: "CLASS_AETHER_WEAVER", icon: "✨" },
|
|
];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const header = queryShadow(".header");
|
|
expect(header).to.exist;
|
|
expect(header.textContent).to.include("MISSION DEPLOYMENT");
|
|
|
|
const unitCards = queryShadowAll(".unit-card");
|
|
expect(unitCards.length).to.equal(2);
|
|
});
|
|
|
|
it("should hide when not in deployment state", async () => {
|
|
element.squad = [{ id: "u1", name: "Test" }];
|
|
element.currentState = "STATE_COMBAT";
|
|
await waitForUpdate();
|
|
|
|
const header = queryShadow(".header");
|
|
expect(header).to.be.null;
|
|
});
|
|
});
|
|
|
|
describe("CoA 2: Tutorial Hints", () => {
|
|
it("should display tutorial hint when missionDef provides one", async () => {
|
|
element.squad = [{ id: "u1", name: "Test" }];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = {
|
|
deployment: {
|
|
tutorial_hint: "Drag units from the bench to the Green Zone.",
|
|
},
|
|
};
|
|
await waitForUpdate();
|
|
|
|
const tutorialHint = queryShadow(".tutorial-hint");
|
|
expect(tutorialHint).to.exist;
|
|
expect(tutorialHint.textContent.trim()).to.equal(
|
|
"Drag units from the bench to the Green Zone."
|
|
);
|
|
});
|
|
|
|
it("should display default hint when no tutorial hint provided", async () => {
|
|
element.squad = [{ id: "u1", name: "Test" }];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = null;
|
|
await waitForUpdate();
|
|
|
|
const tutorialHint = queryShadow(".tutorial-hint");
|
|
expect(tutorialHint).to.be.null;
|
|
|
|
const header = queryShadow(".header");
|
|
expect(header.textContent).to.include(
|
|
"Select a unit below, then click a green tile to place."
|
|
);
|
|
});
|
|
|
|
it("should not display tutorial hint overlay when hint is empty", async () => {
|
|
element.squad = [{ id: "u1", name: "Test" }];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = {
|
|
deployment: {
|
|
tutorial_hint: undefined,
|
|
},
|
|
};
|
|
await waitForUpdate();
|
|
|
|
const tutorialHint = queryShadow(".tutorial-hint");
|
|
expect(tutorialHint).to.be.null;
|
|
});
|
|
});
|
|
|
|
describe("CoA 3: Suggested Units", () => {
|
|
it("should highlight suggested units with suggested class", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Vanguard", classId: "CLASS_VANGUARD", icon: "🛡️" },
|
|
{ id: "u2", name: "Weaver", classId: "CLASS_AETHER_WEAVER", icon: "✨" },
|
|
{ id: "u3", name: "Scavenger", classId: "CLASS_SCAVENGER", icon: "🔧" },
|
|
];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = {
|
|
deployment: {
|
|
suggested_units: ["CLASS_VANGUARD", "CLASS_AETHER_WEAVER"],
|
|
},
|
|
};
|
|
await waitForUpdate();
|
|
|
|
const unitCards = queryShadowAll(".unit-card");
|
|
expect(unitCards.length).to.equal(3);
|
|
|
|
// Check that suggested units have the 'suggested' attribute
|
|
const vanguardCard = Array.from(unitCards).find((card) =>
|
|
card.textContent.includes("Vanguard")
|
|
);
|
|
const weaverCard = Array.from(unitCards).find((card) =>
|
|
card.textContent.includes("Weaver")
|
|
);
|
|
const scavengerCard = Array.from(unitCards).find((card) =>
|
|
card.textContent.includes("Scavenger")
|
|
);
|
|
|
|
expect(vanguardCard?.hasAttribute("suggested")).to.be.true;
|
|
expect(weaverCard?.hasAttribute("suggested")).to.be.true;
|
|
expect(scavengerCard?.hasAttribute("suggested")).to.be.false;
|
|
});
|
|
|
|
it("should display RECOMMENDED label on suggested units", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Vanguard", classId: "CLASS_VANGUARD", icon: "🛡️" },
|
|
];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = {
|
|
deployment: {
|
|
suggested_units: ["CLASS_VANGUARD"],
|
|
},
|
|
};
|
|
await waitForUpdate();
|
|
|
|
const unitCard = queryShadow(".unit-card");
|
|
expect(unitCard.textContent).to.include("RECOMMENDED");
|
|
});
|
|
|
|
it("should not show RECOMMENDED on deployed suggested units", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Vanguard", classId: "CLASS_VANGUARD", icon: "🛡️" },
|
|
];
|
|
element.deployedIndices = [0]; // Unit is deployed
|
|
element.deployedIds = []; // Initialize empty, will be updated from indices
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = {
|
|
deployment: {
|
|
suggested_units: ["CLASS_VANGUARD"],
|
|
},
|
|
};
|
|
await waitForUpdate();
|
|
|
|
const unitCard = queryShadow(".unit-card");
|
|
expect(unitCard.textContent).to.include("DEPLOYED");
|
|
expect(unitCard.textContent).to.not.include("RECOMMENDED");
|
|
});
|
|
|
|
it("should handle empty suggested_units array", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Vanguard", classId: "CLASS_VANGUARD", icon: "🛡️" },
|
|
];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = {
|
|
deployment: {
|
|
suggested_units: [],
|
|
},
|
|
};
|
|
await waitForUpdate();
|
|
|
|
const unitCard = queryShadow(".unit-card");
|
|
expect(unitCard?.hasAttribute("suggested")).to.be.false;
|
|
});
|
|
|
|
it("should handle missing deployment config gracefully", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Vanguard", classId: "CLASS_VANGUARD", icon: "🛡️" },
|
|
];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = {}; // No deployment config
|
|
await waitForUpdate();
|
|
|
|
const unitCard = queryShadow(".unit-card");
|
|
expect(unitCard?.hasAttribute("suggested")).to.be.false;
|
|
|
|
const tutorialHint = queryShadow(".tutorial-hint");
|
|
expect(tutorialHint).to.be.null;
|
|
});
|
|
});
|
|
|
|
describe("CoA 4: Deployment State", () => {
|
|
it("should show deployment count and max units", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Vanguard" },
|
|
{ id: "u2", name: "Weaver" },
|
|
];
|
|
element.deployedIndices = [0]; // Deploy first unit
|
|
element.deployedIds = []; // Initialize empty, will be updated from indices
|
|
element.maxUnits = 4;
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const statusBar = queryShadow(".status-bar");
|
|
expect(statusBar.textContent).to.include("Squad Size: 1 / 4");
|
|
});
|
|
|
|
it("should disable start button when no units deployed", async () => {
|
|
element.squad = [{ id: "u1", name: "Vanguard" }];
|
|
element.deployedIndices = [];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const startBtn = queryShadow(".start-btn");
|
|
expect(startBtn?.hasAttribute("disabled")).to.be.true;
|
|
});
|
|
|
|
it("should enable start button when units are deployed", async () => {
|
|
element.squad = [{ id: "u1", name: "Vanguard" }];
|
|
element.deployedIndices = [0]; // Deploy first unit
|
|
element.deployedIds = []; // Initialize empty, will be updated from indices
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const startBtn = queryShadow(".start-btn");
|
|
expect(startBtn?.hasAttribute("disabled")).to.be.false;
|
|
});
|
|
});
|
|
|
|
describe("CoA 5: Unit Name and Class Display", () => {
|
|
it("should display character name and class name separately", async () => {
|
|
element.squad = [
|
|
{
|
|
id: "u1",
|
|
name: "Valerius",
|
|
className: "Vanguard",
|
|
classId: "CLASS_VANGUARD",
|
|
},
|
|
];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const unitCard = queryShadow(".unit-card");
|
|
const unitName = unitCard?.querySelector(".unit-name");
|
|
const unitClass = unitCard?.querySelector(".unit-class");
|
|
|
|
expect(unitName?.textContent.trim()).to.equal("Valerius");
|
|
expect(unitClass?.textContent.trim()).to.equal("Vanguard");
|
|
});
|
|
|
|
it("should format classId to className when className is missing", async () => {
|
|
element.squad = [
|
|
{
|
|
id: "u1",
|
|
name: "Aria",
|
|
classId: "CLASS_AETHER_WEAVER",
|
|
},
|
|
];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const unitCard = queryShadow(".unit-card");
|
|
const unitClass = unitCard?.querySelector(".unit-class");
|
|
|
|
expect(unitClass?.textContent.trim()).to.equal("Aether Weaver");
|
|
});
|
|
|
|
it("should handle missing name gracefully", async () => {
|
|
element.squad = [
|
|
{
|
|
id: "u1",
|
|
classId: "CLASS_VANGUARD",
|
|
className: "Vanguard",
|
|
},
|
|
];
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const unitCard = queryShadow(".unit-card");
|
|
const unitName = unitCard?.querySelector(".unit-name");
|
|
|
|
expect(unitName?.textContent.trim()).to.equal("Unknown");
|
|
});
|
|
});
|
|
|
|
describe("CoA 6: Deployed Units", () => {
|
|
it("should convert deployed indices to IDs and apply deployed styling", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Valerius", className: "Vanguard" },
|
|
{ id: "u2", name: "Aria", className: "Weaver" },
|
|
{ id: "u3", name: "Kael", className: "Scavenger" },
|
|
];
|
|
element.deployedIndices = [0, 2]; // Deploy units at indices 0 and 2
|
|
element.deployedIds = []; // Initialize empty
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const unitCards = queryShadowAll(".unit-card");
|
|
expect(unitCards.length).to.equal(3);
|
|
|
|
// Check deployed attribute
|
|
expect(unitCards[0].hasAttribute("deployed")).to.be.true;
|
|
expect(unitCards[1].hasAttribute("deployed")).to.be.false;
|
|
expect(unitCards[2].hasAttribute("deployed")).to.be.true;
|
|
|
|
// Check deployed count
|
|
const statusBar = queryShadow(".status-bar");
|
|
expect(statusBar.textContent).to.include("Squad Size: 2 / 4");
|
|
});
|
|
|
|
it("should update deployedIds when squad changes", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Valerius" },
|
|
{ id: "u2", name: "Aria" },
|
|
];
|
|
element.deployedIndices = [0];
|
|
element.deployedIds = []; // Initialize empty
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
// Change squad
|
|
element.squad = [
|
|
{ id: "u3", name: "Kael" },
|
|
{ id: "u4", name: "Lyra" },
|
|
];
|
|
element.deployedIndices = [1];
|
|
await waitForUpdate();
|
|
|
|
const unitCards = queryShadowAll(".unit-card");
|
|
expect(unitCards[0].hasAttribute("deployed")).to.be.false;
|
|
expect(unitCards[1].hasAttribute("deployed")).to.be.true;
|
|
});
|
|
|
|
it("should handle deployment-update event", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Valerius" },
|
|
{ id: "u2", name: "Aria" },
|
|
];
|
|
element.deployedIndices = [];
|
|
element.deployedIds = []; // Initialize empty
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
// Simulate deployment-update event
|
|
window.dispatchEvent(
|
|
new CustomEvent("deployment-update", {
|
|
detail: { deployedIndices: [0] },
|
|
})
|
|
);
|
|
await waitForUpdate();
|
|
|
|
const unitCards = queryShadowAll(".unit-card");
|
|
expect(unitCards[0].hasAttribute("deployed")).to.be.true;
|
|
expect(unitCards[1].hasAttribute("deployed")).to.be.false;
|
|
});
|
|
});
|
|
|
|
describe("CoA 7: Selected Units", () => {
|
|
it("should highlight selected unit", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Valerius", className: "Vanguard" },
|
|
{ id: "u2", name: "Aria", className: "Weaver" },
|
|
];
|
|
element.selectedId = "u1";
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
await waitForUpdate();
|
|
|
|
const unitCards = queryShadowAll(".unit-card");
|
|
expect(unitCards[0].hasAttribute("selected")).to.be.true;
|
|
expect(unitCards[1].hasAttribute("selected")).to.be.false;
|
|
});
|
|
|
|
it("should prioritize selected styling over suggested", async () => {
|
|
element.squad = [
|
|
{ id: "u1", name: "Valerius", className: "Vanguard", classId: "CLASS_VANGUARD" },
|
|
];
|
|
element.selectedId = "u1";
|
|
element.deployedIds = [];
|
|
element.currentState = "STATE_DEPLOYMENT";
|
|
element.missionDef = {
|
|
deployment: {
|
|
suggested_units: ["CLASS_VANGUARD"],
|
|
},
|
|
};
|
|
await waitForUpdate();
|
|
|
|
const unitCard = queryShadow(".unit-card");
|
|
expect(unitCard.hasAttribute("selected")).to.be.true;
|
|
expect(unitCard.hasAttribute("suggested")).to.be.true;
|
|
// Both attributes should be present, CSS will handle priority
|
|
// We can't easily test computed styles in this environment, so just verify attributes
|
|
});
|
|
});
|
|
|
|
// Note: Portrait display tests are skipped because image pathing doesn't work
|
|
// correctly in the test environment (404 errors). The portrait functionality
|
|
// is tested through manual/integration testing.
|
|
});
|
|
|