import { expect } from "@esm-bundle/chai"; import { MissionBoard } from "../../src/ui/components/MissionBoard.js"; import { gameStateManager } from "../../src/core/GameStateManager.js"; describe("UI: MissionBoard", () => { let element; let container; let mockMissionManager; beforeEach(() => { container = document.createElement("div"); document.body.appendChild(container); element = document.createElement("mission-board"); container.appendChild(element); // Mock MissionManager mockMissionManager = { missionRegistry: new Map(), completedMissions: new Set(), }; gameStateManager.missionManager = mockMissionManager; }); afterEach(() => { if (container && container.parentNode) { container.parentNode.removeChild(container); } }); // Helper to wait for LitElement update async function waitForUpdate() { await element.updateComplete; 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("Mission Display", () => { it("should display registered missions", async () => { const mission1 = { id: "MISSION_TUTORIAL_01", type: "TUTORIAL", config: { title: "Protocol: First Descent", description: "Establish a foothold in the Rusting Wastes.", difficulty_tier: 1, }, rewards: { guaranteed: { xp: 100, currency: { aether_shards: 50, }, }, }, }; const mission2 = { id: "MISSION_01", type: "STORY", config: { title: "The First Strike", description: "Strike back at the enemy.", recommended_level: 2, }, rewards: { guaranteed: { xp: 200, currency: { aether_shards: 100, ancient_cores: 1, }, }, }, }; mockMissionManager.missionRegistry.set(mission1.id, mission1); mockMissionManager.missionRegistry.set(mission2.id, mission2); await waitForUpdate(); const missionCards = queryShadowAll(".mission-card"); expect(missionCards.length).to.equal(2); const titles = Array.from(missionCards).map((card) => card.querySelector(".mission-title")?.textContent.trim() ); expect(titles).to.include("Protocol: First Descent"); expect(titles).to.include("The First Strike"); }); it("should show empty state when no missions available", async () => { mockMissionManager.missionRegistry.clear(); await waitForUpdate(); const emptyState = queryShadow(".empty-state"); expect(emptyState).to.exist; expect(emptyState.textContent).to.include("No missions available"); }); }); describe("Mission Card Details", () => { it("should display mission type badge", async () => { const mission = { id: "MISSION_01", type: "STORY", config: { title: "Test Mission", description: "Test" }, rewards: {}, }; mockMissionManager.missionRegistry.set(mission.id, mission); await waitForUpdate(); const missionCard = queryShadow(".mission-card"); const typeBadge = missionCard.querySelector(".mission-type.STORY"); expect(typeBadge).to.exist; expect(typeBadge.textContent).to.include("STORY"); }); it("should display mission description", async () => { const mission = { id: "MISSION_01", type: "TUTORIAL", config: { title: "Test Mission", description: "This is a test mission description.", }, rewards: {}, }; mockMissionManager.missionRegistry.set(mission.id, mission); await waitForUpdate(); const missionCard = queryShadow(".mission-card"); const description = missionCard.querySelector(".mission-description"); expect(description).to.exist; expect(description.textContent).to.include("This is a test mission description."); }); it("should display difficulty information", async () => { const mission = { id: "MISSION_01", type: "TUTORIAL", config: { title: "Test Mission", difficulty_tier: 3, }, rewards: {}, }; mockMissionManager.missionRegistry.set(mission.id, mission); await waitForUpdate(); const missionCard = queryShadow(".mission-card"); const difficulty = missionCard.querySelector(".difficulty"); expect(difficulty).to.exist; expect(difficulty.textContent).to.include("Tier 3"); }); }); describe("Rewards Display", () => { it("should display currency rewards", async () => { const mission = { id: "MISSION_01", type: "TUTORIAL", config: { title: "Test Mission", description: "Test" }, rewards: { guaranteed: { currency: { aether_shards: 150, ancient_cores: 2, }, }, }, }; mockMissionManager.missionRegistry.set(mission.id, mission); await waitForUpdate(); const missionCard = queryShadow(".mission-card"); const rewards = missionCard.querySelector(".mission-rewards"); expect(rewards).to.exist; expect(rewards.textContent).to.include("150"); expect(rewards.textContent).to.include("2"); }); it("should display XP rewards", async () => { const mission = { id: "MISSION_01", type: "TUTORIAL", config: { title: "Test Mission", description: "Test" }, rewards: { guaranteed: { xp: 250, }, }, }; mockMissionManager.missionRegistry.set(mission.id, mission); await waitForUpdate(); const missionCard = queryShadow(".mission-card"); const rewards = missionCard.querySelector(".mission-rewards"); expect(rewards).to.exist; expect(rewards.textContent).to.include("250"); expect(rewards.textContent).to.include("XP"); }); it("should handle both snake_case and camelCase currency", async () => { // Test snake_case (from JSON) const mission1 = { id: "MISSION_01", type: "TUTORIAL", config: { title: "Test 1", description: "Test" }, rewards: { guaranteed: { currency: { aether_shards: 100, }, }, }, }; // Test camelCase (from code) const mission2 = { id: "MISSION_02", type: "TUTORIAL", config: { title: "Test 2", description: "Test" }, rewards: { guaranteed: { currency: { aetherShards: 200, }, }, }, }; mockMissionManager.missionRegistry.set(mission1.id, mission1); mockMissionManager.missionRegistry.set(mission2.id, mission2); await waitForUpdate(); const missionCards = queryShadowAll(".mission-card"); expect(missionCards.length).to.equal(2); // Both should display rewards correctly const rewards1 = missionCards[0].querySelector(".mission-rewards"); const rewards2 = missionCards[1].querySelector(".mission-rewards"); expect(rewards1.textContent).to.include("100"); expect(rewards2.textContent).to.include("200"); }); }); describe("Completed Missions", () => { it("should mark completed missions", async () => { const mission = { id: "MISSION_TUTORIAL_01", type: "TUTORIAL", config: { title: "Test Mission", description: "Test" }, rewards: {}, }; mockMissionManager.missionRegistry.set(mission.id, mission); mockMissionManager.completedMissions.add(mission.id); await waitForUpdate(); const missionCard = queryShadow(".mission-card"); expect(missionCard.classList.contains("completed")).to.be.true; expect(missionCard.textContent).to.include("Completed"); }); it("should not show select button for completed missions", async () => { const mission = { id: "MISSION_TUTORIAL_01", type: "TUTORIAL", config: { title: "Test Mission", description: "Test" }, rewards: {}, }; mockMissionManager.missionRegistry.set(mission.id, mission); mockMissionManager.completedMissions.add(mission.id); await waitForUpdate(); const missionCard = queryShadow(".mission-card"); const selectButton = missionCard.querySelector(".select-button"); expect(selectButton).to.be.null; }); }); describe("Mission Selection", () => { it("should dispatch mission-selected event when mission is clicked", async () => { const mission = { id: "MISSION_TUTORIAL_01", type: "TUTORIAL", config: { title: "Test Mission", description: "Test" }, rewards: {}, }; mockMissionManager.missionRegistry.set(mission.id, mission); await waitForUpdate(); let eventDispatched = false; let eventData = null; element.addEventListener("mission-selected", (e) => { eventDispatched = true; eventData = e.detail; }); const missionCard = queryShadow(".mission-card"); missionCard.click(); await waitForUpdate(); expect(eventDispatched).to.be.true; expect(eventData.missionId).to.equal("MISSION_TUTORIAL_01"); }); it("should dispatch mission-selected event when select button is clicked", async () => { const mission = { id: "MISSION_TUTORIAL_01", type: "TUTORIAL", config: { title: "Test Mission", description: "Test" }, rewards: {}, }; mockMissionManager.missionRegistry.set(mission.id, mission); await waitForUpdate(); let eventDispatched = false; let eventData = null; element.addEventListener("mission-selected", (e) => { eventDispatched = true; eventData = e.detail; }); const selectButton = queryShadow(".select-button"); expect(selectButton).to.exist; selectButton.click(); await waitForUpdate(); expect(eventDispatched).to.be.true; expect(eventData.missionId).to.equal("MISSION_TUTORIAL_01"); }); }); describe("Close Button", () => { it("should dispatch close event when close button is clicked", async () => { const mission = { id: "MISSION_01", type: "TUTORIAL", config: { title: "Test Mission", description: "Test" }, rewards: {}, }; mockMissionManager.missionRegistry.set(mission.id, mission); await waitForUpdate(); let closeEventDispatched = false; element.addEventListener("close", () => { closeEventDispatched = true; }); const closeButton = queryShadow(".close-button"); expect(closeButton).to.exist; closeButton.click(); await waitForUpdate(); expect(closeEventDispatched).to.be.true; }); }); describe("Mission Type Styling", () => { it("should apply correct styling for different mission types", async () => { const missions = [ { id: "M1", type: "STORY", config: { title: "Story", description: "Test" }, rewards: {} }, { id: "M2", type: "SIDE_QUEST", config: { title: "Side", description: "Test" }, rewards: {} }, { id: "M3", type: "TUTORIAL", config: { title: "Tutorial", description: "Test" }, rewards: {} }, { id: "M4", type: "PROCEDURAL", config: { title: "Proc", description: "Test" }, rewards: {} }, ]; missions.forEach((m) => mockMissionManager.missionRegistry.set(m.id, m)); await waitForUpdate(); const missionCards = queryShadowAll(".mission-card"); expect(missionCards.length).to.equal(4); const typeBadges = Array.from(missionCards).map((card) => card.querySelector(".mission-type") ); expect(typeBadges[0].classList.contains("STORY")).to.be.true; expect(typeBadges[1].classList.contains("SIDE_QUEST")).to.be.true; expect(typeBadges[2].classList.contains("TUTORIAL")).to.be.true; expect(typeBadges[3].classList.contains("PROCEDURAL")).to.be.true; }); }); });