aether-shards/test/ui/deployment-hud.test.js

434 lines
14 KiB
JavaScript
Raw Normal View History

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.
});