import { describe, it, expect, beforeEach } from 'vitest' import { SkillHotkeysHud, DEFAULT_SORCERESS_SKILLS, LEFT_SKILL_BOUNDS, RIGHT_SKILL_BOUNDS } from '../src/ui/hotkeys.ts' import { HudManager } from '../src/ui/hud-manager.ts' import { CLASS_DEFAULT_SKILLS, createStarterProfileForClass } from '../src/game/class-starter-profiles.ts' import { SKILLS_BY_ID } from '../src/data/skills-catalog.ts' describe('Issue Fix: Glacial Spike (#55) and All Active Skills on Quickbar Speedbars', () => { let hud: SkillHotkeysHud beforeEach(() => { hud = new SkillHotkeysHud() }) describe('1. Sorceress Right-Click & Left-Click Quickbar includes Glacial Spike', () => { it('has Glacial Spike (55) in DEFAULT_SORCERESS_SKILLS with level 10 and authentic mana', () => { const entry = DEFAULT_SORCERESS_SKILLS.find(s => s.skillId === 55) expect(entry).toBeDefined() expect(entry!.name).toBe('Glacial Spike') expect(entry!.nameZh).toBe('冰尖柱') expect(entry!.level).toBe(10) expect(entry!.manaCost).toBeCloseTo(14.5, 1) expect(entry!.leftUsable).toBe(true) expect(entry!.rightUsable).toBe(true) }) it('shows Glacial Spike in right-click speedbar popup cells', () => { const rightCells = hud.getSpeedbarSkills('right') const spikeCell = rightCells.find(c => c.skill.skillId === 55) expect(spikeCell).toBeDefined() expect(spikeCell!.skill.name).toBe('Glacial Spike') expect(spikeCell!.skill.nameZh).toBe('冰尖柱') }) it('shows Glacial Spike in left-click speedbar popup cells', () => { const leftCells = hud.getSpeedbarSkills('left') const spikeCell = leftCells.find(c => c.skill.skillId === 55) expect(spikeCell).toBeDefined() expect(spikeCell!.skill.name).toBe('Glacial Spike') }) it('assigns Glacial Spike to right-click slot when clicked in right speedbar', () => { hud.openSpeedbar('right') expect(hud.selectionOpen).toBe('right') const rightCells = hud.getSpeedbarSkills('right') const spikeCell = rightCells.find(c => c.skill.skillId === 55)! expect(spikeCell).toBeDefined() const handled = hud.handleClick(spikeCell.x + 10, spikeCell.y + 10) expect(handled).toBe(true) expect(hud.rightSkillId).toBe(55) expect(hud.selectionOpen).toBeNull() }) it('assigns Glacial Spike to left-click slot when clicked in left speedbar', () => { hud.openSpeedbar('left') expect(hud.selectionOpen).toBe('left') const leftCells = hud.getSpeedbarSkills('left') const spikeCell = leftCells.find(c => c.skill.skillId === 55)! expect(spikeCell).toBeDefined() const handled = hud.handleClick(spikeCell.x + 10, spikeCell.y + 10) expect(handled).toBe(true) expect(hud.leftSkillId).toBe(55) expect(hud.selectionOpen).toBeNull() }) }) describe('2. All 7 Classes Starter Profiles include all Active Skills', () => { it('provides all 26 active skills on Sorceress right-click speedbar', () => { const sorProfile = createStarterProfileForClass('sor') const rightSkills = sorProfile.availableSkills.filter(s => s.rightUsable) // 3 universal (Attack 0, Throw 2, Unsummon 3) + 26 Sorceress active = 29 expect(rightSkills.length).toBe(29) // Glacial Spike is explicitly present expect(rightSkills.some(s => s.skillId === 55)).toBe(true) }) it('provides all active skills for all 7 classes without passives', () => { const classes = ['ama', 'sor', 'nec', 'pal', 'bar', 'dru', 'ass'] as const const expectedActiveCounts: Record = { ama: 27, // 3 universal + 24 active sor: 29, // 3 universal + 26 active nec: 30, // 3 universal + 27 active pal: 33, // 3 universal + 30 active bar: 23, // 3 universal + 20 active dru: 32, // 3 universal + 29 active ass: 31, // 3 universal + 28 active } for (const cls of classes) { const skills = CLASS_DEFAULT_SKILLS[cls] expect(skills.length).toBe(expectedActiveCounts[cls]) // None should be passive for (const s of skills) { expect(s.isPassive).toBeFalsy() } } }) }) describe('3. HudManager Skill Tree Allocation dynamically updates Available Skills', () => { const mockCanvas = { width: 800, height: 600, style: { cursor: '' }, addEventListener: () => {}, removeEventListener: () => {}, getContext: () => null, } as unknown as HTMLCanvasElement function createMockHud() { const hud = new HudManager(mockCanvas, { onToggleAutomap: () => {}, onWaypointTeleport: () => {}, }) hud.switchClass('sor') return hud } it('initializes HudManager with Glacial Spike available in Sorceress profile', () => { const hudManager = createMockHud() expect(hudManager.hotkeys.availableSkills.some(s => s.skillId === 55)).toBe(true) }) it('allocates new skill in skill tree and adds it to hotkeys.availableSkills', () => { const hudManager = createMockHud() // Open Skill tree panel hudManager.rightPanel = 'skill' hudManager.skillTree.visible = true hudManager.skillTree.unspentSkillPoints = 5 // Select lightning tab (tab 1) hudManager.skillTree.activeTab = 1 // Find Nova (48) which requires level 12 and has reqskills: Static Field (42) which is already allocated in starter const nova = hudManager.hotkeys.availableSkills.find(s => s.skillId === 48) expect(nova).toBeDefined() // Allocate point into Nova by clicking on it const node = hudManager.skillTree.getSkillTree().find(n => n.skillId === 48)! expect(node).toBeDefined() const prevPoints = hudManager.skillTree.hardPoints.get(48) ?? 0 const allocated = hudManager.skillTree.allocateSkill(48) expect(allocated).toBe(true) // Test handleSkillAllocated const privateMethod = (hudManager as any).handleSkillAllocated.bind(hudManager) privateMethod(48) const updatedNova = hudManager.hotkeys.availableSkills.find(s => s.skillId === 48) expect(updatedNova).toBeDefined() expect(updatedNova!.level).toBe(prevPoints + 1) }) }) })