aether-shards/test/ui/character-sheet.test.js
Matthew Mone 2c86d674f4 Add mission debrief and procedural mission generation features
- Introduce the MissionDebrief component to display after-action reports, including XP, rewards, and squad status.
- Implement the MissionGenerator class to create procedural side missions, enhancing replayability and resource management.
- Update mission schema to include mission objects for INTERACT objectives, improving mission complexity.
- Enhance GameLoop and MissionManager to support new mission features and interactions.
- Add tests for MissionDebrief and MissionGenerator to ensure functionality and integration within the game architecture.
2026-01-01 16:08:54 -08:00

905 lines
28 KiB
JavaScript

import { expect } from "@esm-bundle/chai";
import { CharacterSheet } from "../../src/ui/components/character-sheet.js";
import { Explorer } from "../../src/units/Explorer.js";
import { Item } from "../../src/items/Item.js";
import vanguardDef from "../../src/assets/data/classes/vanguard.json" with {
type: "json",
};
// Import SkillTreeUI to register the custom element
import "../../src/ui/components/skill-tree-ui.js";
describe("UI: CharacterSheet", () => {
let element;
let container;
let testUnit;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
element = document.createElement("character-sheet");
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: 2,
unlockedNodes: [],
};
testUnit.recalculateBaseStats(vanguardDef);
testUnit.currentHealth = 100;
testUnit.maxHealth = 120;
});
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: Stat Rendering", () => {
it("should render stats with effective values", async () => {
element.unit = testUnit;
await waitForUpdate();
// Check that stat values are displayed
const statValues = queryShadowAll('.stat-value');
expect(statValues.length).to.be.greaterThan(0);
});
it("should show stat breakdown in tooltip on hover", async () => {
element.unit = testUnit;
await waitForUpdate();
const statItem = queryShadow(".stat-item");
expect(statItem).to.exist;
const tooltip = statItem.querySelector(".tooltip");
expect(tooltip).to.exist;
});
it("should display debuffed stats in red", async () => {
testUnit.statusEffects = [
{
id: "WEAKNESS",
name: "Weakness",
statModifiers: { attack: -5 },
},
];
element.unit = testUnit;
await waitForUpdate();
const attackStat = Array.from(queryShadowAll(".stat-item")).find((item) =>
item.textContent.includes("Attack")
);
expect(attackStat).to.exist;
expect(attackStat.classList.contains("debuffed")).to.be.true;
const statValue = attackStat.querySelector(".stat-value");
expect(statValue.classList.contains("debuffed")).to.be.true;
});
it("should display buffed stats in green", async () => {
testUnit.statusEffects = [
{
id: "STRENGTH",
name: "Strength",
statModifiers: { attack: +5 },
},
];
element.unit = testUnit;
await waitForUpdate();
const attackStat = Array.from(queryShadowAll(".stat-item")).find((item) =>
item.textContent.includes("Attack")
);
expect(attackStat).to.exist;
expect(attackStat.classList.contains("buffed")).to.be.true;
const statValue = attackStat.querySelector(".stat-value");
expect(statValue.classList.contains("buffed")).to.be.true;
});
it("should calculate effective stats from base + equipment + buffs", async () => {
const weapon = new Item({
id: "ITEM_TEST_SWORD",
name: "Test Sword",
type: "WEAPON",
stats: { attack: 10 },
});
// Reset to level 1 to get base stats
testUnit.classMastery["CLASS_VANGUARD"].level = 1;
testUnit.recalculateBaseStats(vanguardDef);
testUnit.equipment.weapon = weapon;
testUnit.statusEffects = [
{
id: "BUFF",
name: "Power Boost",
statModifiers: { attack: 3 },
},
];
element.unit = testUnit;
await waitForUpdate();
// Base attack from vanguard level 1 is 12, +10 from weapon, +3 from buff = 25
const attackStat = Array.from(queryShadowAll(".stat-item")).find((item) =>
item.textContent.includes("Attack")
);
const statValue = attackStat.querySelector(".stat-value");
const totalValue = parseInt(statValue.textContent.trim());
expect(totalValue).to.equal(25);
});
it("should display health bar with current/max values", async () => {
testUnit.currentHealth = 80;
testUnit.maxHealth = 120;
element.unit = testUnit;
await waitForUpdate();
const healthBar = queryShadow(".health-bar-container");
expect(healthBar).to.exist;
const healthLabel = queryShadow(".health-label");
expect(healthLabel.textContent).to.include("80");
expect(healthLabel.textContent).to.include("120");
});
it("should display AP icons based on speed", async () => {
// Speed = 8, so AP = 3 + floor(8/5) = 3 + 1 = 4
testUnit.baseStats.speed = 8;
testUnit.currentAP = 3;
element.unit = testUnit;
await waitForUpdate();
// Find the AP stat item (which shows AP icons)
const apStat = Array.from(queryShadowAll(".stat-item")).find((item) =>
item.textContent.includes("AP")
);
expect(apStat).to.exist;
const apIcons = apStat.querySelectorAll(".ap-icon");
expect(apIcons.length).to.equal(4); // Max AP
const emptyIcons = apStat.querySelectorAll(".ap-icon.empty");
expect(emptyIcons.length).to.equal(1); // One empty (4 total - 3 used)
});
});
describe("CoA 2: Equipment Swapping", () => {
it("should show equipment slots in paper doll", async () => {
element.unit = testUnit;
await waitForUpdate();
// Use the new class names (mainHand, offHand, body, accessory)
const weaponSlot = queryShadow(".equipment-slot.mainHand");
const armorSlot = queryShadow(".equipment-slot.body");
const relicSlot = queryShadow(".equipment-slot.accessory");
const utilitySlot = queryShadow(".equipment-slot.offHand");
expect(weaponSlot).to.exist;
expect(armorSlot).to.exist;
expect(relicSlot).to.exist;
expect(utilitySlot).to.exist;
});
it("should show ghost icon for empty slots", async () => {
element.unit = testUnit;
await waitForUpdate();
const weaponSlot = queryShadow(".equipment-slot.mainHand");
const slotIcon = weaponSlot.querySelector(".slot-icon");
expect(slotIcon).to.exist;
});
it("should show item icon for equipped items", async () => {
// Use the new loadout system
testUnit.loadout.mainHand = {
uid: "ITEM_TEST_SWORD_1",
defId: "ITEM_TEST_SWORD",
isNew: false,
quantity: 1,
};
// Mock inventoryManager with item registry
const mockItemRegistry = new Map();
mockItemRegistry.set("ITEM_TEST_SWORD", {
id: "ITEM_TEST_SWORD",
name: "Test Sword",
type: "WEAPON",
icon: "⚔️",
});
element.unit = testUnit;
element.inventoryManager = {
itemRegistry: mockItemRegistry,
};
await waitForUpdate();
const weaponSlot = queryShadow(".equipment-slot.mainHand");
const itemIcon = weaponSlot.querySelector(".item-icon");
expect(itemIcon).to.exist;
});
it("should switch to inventory tab when slot is clicked", async () => {
element.unit = testUnit;
element.inventory = [];
await waitForUpdate();
const weaponSlot = queryShadow(".equipment-slot.mainHand");
weaponSlot.click();
await waitForUpdate();
expect(element.activeTab).to.equal("INVENTORY");
expect(element.selectedSlot).to.equal("MAIN_HAND");
});
it("should filter inventory by slot type when slot is selected", async () => {
const weapon1 = new Item({
id: "ITEM_SWORD",
name: "Sword",
type: "WEAPON",
});
const weapon2 = new Item({
id: "ITEM_AXE",
name: "Axe",
type: "WEAPON",
});
const armor = new Item({
id: "ITEM_PLATE",
name: "Plate",
type: "ARMOR",
});
element.unit = testUnit;
element.inventory = [weapon1, weapon2, armor];
element.selectedSlot = "WEAPON";
await waitForUpdate();
const itemCards = queryShadowAll(".item-card");
expect(itemCards.length).to.equal(2); // Only weapons
});
it("should equip item when clicked in inventory", async () => {
const weapon = new Item({
id: "ITEM_SWORD",
name: "Sword",
type: "WEAPON",
stats: { attack: 10 },
});
const oldWeapon = new Item({
id: "ITEM_OLD_SWORD",
name: "Old Sword",
type: "WEAPON",
});
testUnit.equipment.weapon = oldWeapon;
element.unit = testUnit;
element.inventory = [weapon];
element.selectedSlot = "WEAPON";
let equipEventFired = false;
let equipEventDetail = null;
element.addEventListener("equip-item", (e) => {
equipEventFired = true;
equipEventDetail = e.detail;
});
await waitForUpdate();
const itemCard = queryShadow(".item-card");
itemCard.click();
await waitForUpdate();
expect(equipEventFired).to.be.true;
expect(equipEventDetail.unitId).to.equal(testUnit.id);
expect(equipEventDetail.slot).to.equal("WEAPON");
expect(equipEventDetail.item.id).to.equal("ITEM_SWORD");
expect(equipEventDetail.oldItem.id).to.equal("ITEM_OLD_SWORD");
// Old item should be in inventory
expect(element.inventory.some((item) => item.id === "ITEM_OLD_SWORD")).to.be
.true;
// New item should be equipped
expect(testUnit.equipment.weapon.id).to.equal("ITEM_SWORD");
});
it("should update stats immediately after equipping", async () => {
const weapon = new Item({
id: "ITEM_SWORD",
name: "Sword",
type: "WEAPON",
stats: { attack: 15 },
});
element.unit = testUnit;
element.inventory = [weapon];
element.selectedSlot = "WEAPON";
await waitForUpdate();
const initialAttack = Array.from(queryShadowAll(".stat-item")).find((item) =>
item.textContent.includes("Attack")
);
const initialValue = parseInt(
initialAttack.querySelector(".stat-value").textContent.trim()
);
const itemCard = queryShadow(".item-card");
itemCard.click();
await waitForUpdate();
const updatedAttack = Array.from(queryShadowAll(".stat-item")).find((item) =>
item.textContent.includes("Attack")
);
const updatedValue = parseInt(
updatedAttack.querySelector(".stat-value").textContent.trim()
);
expect(updatedValue).to.equal(initialValue + 15);
});
it("should not allow equipping in read-only mode", async () => {
element.unit = testUnit;
element.readOnly = true;
element.inventory = [
new Item({
id: "ITEM_SWORD",
name: "Sword",
type: "WEAPON",
}),
];
element.selectedSlot = "MAIN_HAND";
await waitForUpdate();
const weaponSlot = queryShadow(".equipment-slot.mainHand");
expect(weaponSlot.hasAttribute("disabled")).to.be.true;
const itemCard = queryShadow(".item-card");
const initialWeapon = testUnit.equipment.weapon;
itemCard.click();
await waitForUpdate();
// Equipment should not have changed
expect(testUnit.equipment.weapon).to.equal(initialWeapon);
});
});
describe("CoA 3: Skill Interaction", () => {
it("should display skill tree tab", async () => {
element.unit = testUnit;
element.activeTab = "SKILLS";
await waitForUpdate();
const skillsTab = Array.from(queryShadowAll(".tab-button")).find((btn) =>
btn.textContent.includes("Skills")
);
expect(skillsTab).to.exist;
const skillsContainer = queryShadow(".skills-container");
expect(skillsContainer).to.exist;
});
it("should embed skill-tree-ui component", async () => {
element.unit = testUnit;
element.activeTab = "SKILLS";
await waitForUpdate();
const skillTree = queryShadow("skill-tree-ui");
expect(skillTree).to.exist;
expect(skillTree.unit).to.equal(testUnit);
});
it("should display SP badge when skill points are available", async () => {
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 3;
element.unit = testUnit;
await waitForUpdate();
const spBadge = queryShadow(".sp-badge");
expect(spBadge).to.exist;
expect(spBadge.textContent).to.include("SP: 3");
});
it("should not display SP badge when no skill points", async () => {
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 0;
element.unit = testUnit;
await waitForUpdate();
const spBadge = queryShadow(".sp-badge");
expect(spBadge).to.be.null;
});
it("should handle unlock-request and update unit stats", async () => {
// Set up unit with skill points and mock recalculateStats
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 2;
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = [];
testUnit.maxHealth = 100;
testUnit.currentHealth = 100;
let recalculateStatsCalled = false;
let recalculateStatsArgs = null;
testUnit.recalculateStats = (itemRegistry, treeDef) => {
recalculateStatsCalled = true;
recalculateStatsArgs = { itemRegistry, treeDef };
// Simulate stat boost application
testUnit.maxHealth = 110; // Base 100 + 10 from health boost
};
element.unit = testUnit;
element.activeTab = "SKILLS";
await waitForUpdate();
// Create mock tree definition
const mockTreeDef = {
id: "TREE_TEST",
nodes: {
ROOT: {
id: "ROOT",
tier: 1,
type: "STAT_BOOST",
data: { stat: "health", value: 10 },
req: 1,
cost: 1,
},
},
};
element.treeDef = mockTreeDef;
await waitForUpdate();
// Call the handler directly (since it's a private method, we'll simulate the event)
const unlockEvent = new CustomEvent("unlock-request", {
detail: { nodeId: "ROOT", cost: 1 },
bubbles: true,
composed: true,
});
// Simulate the event being handled by calling the method directly
element._handleUnlockRequest(unlockEvent);
await waitForUpdate();
// Verify node was unlocked
expect(testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes).to.include("ROOT");
expect(testUnit.classMastery["CLASS_VANGUARD"].skillPoints).to.equal(1);
// Verify recalculateStats was called with correct args
expect(recalculateStatsCalled).to.be.true;
expect(recalculateStatsArgs.treeDef).to.exist;
});
it("should dispatch skill-unlocked event after unlocking", async () => {
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 2;
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = [];
testUnit.recalculateStats = () => {}; // Mock function
element.unit = testUnit;
await waitForUpdate();
let skillUnlockedEventFired = false;
let skillUnlockedEventDetail = null;
element.addEventListener("skill-unlocked", (e) => {
skillUnlockedEventFired = true;
skillUnlockedEventDetail = e.detail;
});
const unlockEvent = new CustomEvent("unlock-request", {
detail: { nodeId: "ROOT", cost: 1 },
bubbles: true,
composed: true,
});
// Call the handler directly
element._handleUnlockRequest(unlockEvent);
await waitForUpdate();
expect(skillUnlockedEventFired).to.be.true;
expect(skillUnlockedEventDetail.unitId).to.equal(testUnit.id);
expect(skillUnlockedEventDetail.nodeId).to.equal("ROOT");
expect(skillUnlockedEventDetail.cost).to.equal(1);
});
it("should update SkillTreeUI after unlocking", async () => {
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 2;
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = [];
testUnit.recalculateStats = () => {}; // Mock function
element.unit = testUnit;
element.activeTab = "SKILLS";
await waitForUpdate();
const skillTree = queryShadow("skill-tree-ui");
expect(skillTree).to.exist;
const initialUpdateTrigger = skillTree.updateTrigger || 0;
const unlockEvent = new CustomEvent("unlock-request", {
detail: { nodeId: "ROOT", cost: 1 },
bubbles: true,
composed: true,
});
// Call the handler directly
element._handleUnlockRequest(unlockEvent);
// Wait for setTimeout to execute
await new Promise((resolve) => setTimeout(resolve, 20));
await waitForUpdate();
// Verify updateTrigger was incremented
expect(skillTree.updateTrigger).to.be.greaterThan(initialUpdateTrigger);
});
it("should include skill tree stat boosts in stat breakdown", async () => {
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = ["ROOT"];
testUnit.baseStats.health = 100;
testUnit.maxHealth = 100;
// Create mock tree definition with health boost
const mockTreeDef = {
id: "TREE_TEST",
nodes: {
ROOT: {
id: "ROOT",
tier: 1,
type: "STAT_BOOST",
data: { stat: "health", value: 10 },
req: 1,
cost: 1,
},
},
};
element.unit = testUnit;
element.treeDef = mockTreeDef;
await waitForUpdate();
// Get health stat breakdown - health uses a health bar, not stat-value
const healthStat = Array.from(queryShadowAll(".stat-item")).find((item) =>
item.textContent.includes("Health")
);
expect(healthStat).to.exist;
// Health shows as "current / max" in the health label
const healthLabel = healthStat.querySelector(".health-label");
if (healthLabel) {
// Health bar shows current/max, so we check the max value
const healthText = healthLabel.textContent;
const match = healthText.match(/\/(\d+)/);
if (match) {
const maxHealth = parseInt(match[1]);
// Should be 100 base + 10 boost = 110
expect(maxHealth).to.equal(110);
}
} else {
// Fallback: check if the breakdown tooltip would show the boost
// This test verifies the calculation happens, even if we can't easily test the UI
const { total } = element._getEffectiveStat("health");
expect(total).to.equal(110);
}
});
describe("30-Node Skill Tree Integration", () => {
it("should receive and pass full 30-node tree to SkillTreeUI", async () => {
// Create a mock 30-node tree (simplified version)
const mock30NodeTree = {
id: "TREE_CLASS_VANGUARD",
nodes: {},
};
// Generate 30 node IDs
for (let i = 1; i <= 30; i++) {
mock30NodeTree.nodes[`NODE_${i}`] = {
id: `NODE_${i}`,
tier: Math.ceil(i / 6),
type: i % 3 === 0 ? "STAT_BOOST" : "ACTIVE_SKILL",
data: i % 3 === 0
? { stat: "health", value: i }
: { id: `SKILL_${i}`, name: `Skill ${i}` },
req: Math.ceil(i / 6),
cost: Math.ceil(i / 10),
children: i < 30 ? [`NODE_${i + 1}`] : [],
};
}
element.unit = testUnit;
element.treeDef = mock30NodeTree;
element.activeTab = "SKILLS";
await waitForUpdate();
const skillTree = queryShadow("skill-tree-ui");
expect(skillTree).to.exist;
expect(skillTree.treeDef).to.exist;
expect(skillTree.treeDef.id).to.equal("TREE_CLASS_VANGUARD");
expect(Object.keys(skillTree.treeDef.nodes)).to.have.length(30);
});
it("should handle treeDef with all node types from template", async () => {
const mockFullTree = {
id: "TREE_TEST",
nodes: {
NODE_T1_1: {
tier: 1,
type: "STAT_BOOST",
data: { stat: "health", value: 2 },
req: 1,
cost: 1,
children: ["NODE_T2_1", "NODE_T2_2"],
},
NODE_T2_1: {
tier: 2,
type: "STAT_BOOST",
data: { stat: "defense", value: 2 },
req: 2,
cost: 1,
children: ["NODE_T3_1"],
},
NODE_T2_2: {
tier: 2,
type: "ACTIVE_SKILL",
data: { id: "SKILL_1", name: "Shield Bash" },
req: 2,
cost: 1,
children: ["NODE_T3_2"],
},
NODE_T3_1: {
tier: 3,
type: "STAT_BOOST",
data: { stat: "health", value: 6 },
req: 3,
cost: 1,
children: [],
},
NODE_T3_2: {
tier: 3,
type: "ACTIVE_SKILL",
data: { id: "SKILL_2", name: "Taunt" },
req: 3,
cost: 1,
children: [],
},
NODE_T4_1: {
tier: 4,
type: "ACTIVE_SKILL",
data: { id: "SKILL_3", name: "Skill 3" },
req: 4,
cost: 2,
children: [],
},
NODE_T4_2: {
tier: 4,
type: "PASSIVE_ABILITY",
data: { effect_id: "PASSIVE_1", name: "Passive 1" },
req: 4,
cost: 2,
children: [],
},
},
};
element.unit = testUnit;
element.treeDef = mockFullTree;
element.activeTab = "SKILLS";
await waitForUpdate();
const skillTree = queryShadow("skill-tree-ui");
expect(skillTree).to.exist;
expect(skillTree.treeDef).to.equal(mockFullTree);
// Verify all node types are present
const nodes = skillTree.treeDef.nodes;
expect(nodes.NODE_T1_1.type).to.equal("STAT_BOOST");
expect(nodes.NODE_T2_2.type).to.equal("ACTIVE_SKILL");
expect(nodes.NODE_T4_2.type).to.equal("PASSIVE_ABILITY");
});
it("should use treeDef in _getTreeDefinition method", async () => {
const mockTree = {
id: "TREE_TEST",
nodes: {
NODE_1: {
tier: 1,
type: "STAT_BOOST",
data: { stat: "health", value: 2 },
req: 1,
cost: 1,
children: [],
},
},
};
element.unit = testUnit;
element.treeDef = mockTree;
await waitForUpdate();
const treeDef = element._getTreeDefinition();
expect(treeDef).to.equal(mockTree);
expect(treeDef.id).to.equal("TREE_TEST");
});
it("should pass treeDef to recalculateStats when unlocking nodes", async () => {
testUnit.classMastery["CLASS_VANGUARD"].skillPoints = 2;
testUnit.classMastery["CLASS_VANGUARD"].unlockedNodes = [];
testUnit.recalculateStats = () => {}; // Mock function
const mockTree = {
id: "TREE_TEST",
nodes: {
NODE_1: {
tier: 1,
type: "STAT_BOOST",
data: { stat: "health", value: 10 },
req: 1,
cost: 1,
children: [],
},
},
};
let recalculateStatsCalledWith = null;
testUnit.recalculateStats = (itemRegistry, treeDef) => {
recalculateStatsCalledWith = { itemRegistry, treeDef };
};
element.unit = testUnit;
element.treeDef = mockTree;
await waitForUpdate();
const unlockEvent = new CustomEvent("unlock-request", {
detail: { nodeId: "NODE_1", cost: 1 },
bubbles: true,
composed: true,
});
element._handleUnlockRequest(unlockEvent);
await waitForUpdate();
expect(recalculateStatsCalledWith).to.exist;
expect(recalculateStatsCalledWith.treeDef).to.equal(mockTree);
});
});
});
describe("CoA 4: Context Awareness", () => {
it("should display inventory tab", async () => {
element.unit = testUnit;
element.inventory = [];
element.activeTab = "INVENTORY";
await waitForUpdate();
const inventoryGrid = queryShadow(".inventory-grid");
expect(inventoryGrid).to.exist;
});
it("should show empty message when inventory is empty", async () => {
element.unit = testUnit;
element.inventory = [];
element.activeTab = "INVENTORY";
await waitForUpdate();
const emptyMessage = queryShadow(".inventory-grid p");
expect(emptyMessage).to.exist;
expect(emptyMessage.textContent).to.include("No items available");
});
it("should display mastery tab", async () => {
element.unit = testUnit;
element.activeTab = "MASTERY";
await waitForUpdate();
const masteryContainer = queryShadow(".mastery-container");
expect(masteryContainer).to.exist;
});
it("should show mastery progress for all classes", async () => {
testUnit.classMastery["CLASS_VANGUARD"] = {
level: 5,
xp: 250,
skillPoints: 2,
unlockedNodes: [],
};
testUnit.classMastery["CLASS_WEAVER"] = {
level: 2,
xp: 50,
skillPoints: 0,
unlockedNodes: [],
};
element.unit = testUnit;
element.activeTab = "MASTERY";
await waitForUpdate();
const masteryClasses = queryShadowAll(".mastery-class");
expect(masteryClasses.length).to.equal(2);
});
});
describe("Header Rendering", () => {
it("should display unit name, class, and level", async () => {
element.unit = testUnit;
await waitForUpdate();
const name = queryShadow(".name");
expect(name.textContent).to.include("Test Vanguard");
const classTitle = queryShadow(".class-title");
expect(classTitle.textContent).to.include("Vanguard");
const level = queryShadow(".level");
expect(level.textContent).to.include("Level 5");
});
it("should display XP bar", async () => {
element.unit = testUnit;
await waitForUpdate();
const xpBar = queryShadow(".xp-bar-container");
expect(xpBar).to.exist;
const xpLabel = queryShadow(".xp-label");
expect(xpLabel.textContent).to.include("250");
});
it("should display close button", async () => {
element.unit = testUnit;
await waitForUpdate();
const closeButton = queryShadow(".close-button");
expect(closeButton).to.exist;
});
it("should dispatch close event when close button is clicked", async () => {
element.unit = testUnit;
await waitForUpdate();
let closeEventFired = false;
element.addEventListener("close", () => {
closeEventFired = true;
});
const closeButton = queryShadow(".close-button");
closeButton.click();
expect(closeEventFired).to.be.true;
});
});
describe("Tab Switching", () => {
it("should switch between tabs", async () => {
element.unit = testUnit;
await waitForUpdate();
const inventoryTab = queryShadowAll(".tab-button")[0];
const skillsTab = queryShadowAll(".tab-button")[1];
const masteryTab = queryShadowAll(".tab-button")[2];
expect(inventoryTab.classList.contains("active")).to.be.true;
skillsTab.click();
await waitForUpdate();
expect(element.activeTab).to.equal("SKILLS");
expect(skillsTab.classList.contains("active")).to.be.true;
masteryTab.click();
await waitForUpdate();
expect(element.activeTab).to.equal("MASTERY");
expect(masteryTab.classList.contains("active")).to.be.true;
});
});
});