diablo2-web/tests/skill-tree-panel.test.ts

199 lines
5.4 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest'
import { existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { SkillTreePanel, SKILL_PANEL_ORIGIN } from '../src/ui/skill-tree-panel.ts'
import { BAKED_UI_MANIFEST } from '../src/ui/baked-ui-meta.ts'
import type { D2FontRenderer } from '../src/ui/font.ts'
describe('Issue #377: Authentic D2 v1.13c Skill Tree Tabs & Skill Points Banner', () => {
it('manifest contains skillTabs and skillPoints image references', () => {
expect(BAKED_UI_MANIFEST.images.skillTabs).toBe('/ui/skill-tree-tabs.png')
expect(BAKED_UI_MANIFEST.images.skillPoints).toBe('/ui/skillpoints.png')
const tabsPath = join(process.cwd(), 'public', 'ui', 'skill-tree-tabs.png')
const spPath = join(process.cwd(), 'public', 'ui', 'skillpoints.png')
expect(existsSync(tabsPath), 'skill-tree-tabs.png must exist in public/ui').toBe(true)
expect(existsSync(spPath), 'skillpoints.png must exist in public/ui').toBe(true)
})
it('skill-tree-tabs.png has authentic 192x432 strip dimensions (3 states x 64x432)', () => {
const tabsPath = join(process.cwd(), 'public', 'ui', 'skill-tree-tabs.png')
const buf = readFileSync(tabsPath)
// PNG width and height at bytes 16..24
const width = buf.readUInt32BE(16)
const height = buf.readUInt32BE(20)
expect(width).toBe(192)
expect(height).toBe(432)
})
it('skillpoints.png has authentic 135x23 dimensions', () => {
const spPath = join(process.cwd(), 'public', 'ui', 'skillpoints.png')
const buf = readFileSync(spPath)
const width = buf.readUInt32BE(16)
const height = buf.readUInt32BE(20)
expect(width).toBe(135)
expect(height).toBe(23)
})
it('draws authentic 64x432 tab column slice according to activeTab', () => {
const panel = new SkillTreePanel()
panel.visible = true
const mockCtx = {
save: vi.fn(),
restore: vi.fn(),
drawImage: vi.fn(),
fillRect: vi.fn(),
strokeRect: vi.fn(),
fillStyle: '',
strokeStyle: '',
lineWidth: 1,
globalAlpha: 1,
} as unknown as CanvasRenderingContext2D
const mockFont = {
drawText: vi.fn(),
measureText: vi.fn(() => 50),
} as unknown as D2FontRenderer
const mockTabsImg = {
complete: true,
naturalWidth: 192,
naturalHeight: 432,
} as unknown as HTMLImageElement
const mockSpImg = {
complete: true,
naturalWidth: 135,
naturalHeight: 23,
} as unknown as HTMLImageElement
// 1. Tab 2 (Cold Spells, Top Tab): slice sx = 0
panel.activeTab = 2
panel.draw(mockCtx, {
borderRightImg: null,
skillPointsImg: mockSpImg,
buySellBtnImg: null,
skillTabsImg: mockTabsImg,
}, mockFont)
expect(mockCtx.drawImage).toHaveBeenCalledWith(
mockTabsImg,
0, // sx = 0
0,
64,
432,
SKILL_PANEL_ORIGIN.x + 256,
SKILL_PANEL_ORIGIN.y,
64,
432,
)
// 2. Tab 1 (Lightning Spells, Middle Tab): slice sx = 64
vi.clearAllMocks()
panel.activeTab = 1
panel.draw(mockCtx, {
borderRightImg: null,
skillPointsImg: mockSpImg,
buySellBtnImg: null,
skillTabsImg: mockTabsImg,
}, mockFont)
expect(mockCtx.drawImage).toHaveBeenCalledWith(
mockTabsImg,
64, // sx = 64
0,
64,
432,
SKILL_PANEL_ORIGIN.x + 256,
SKILL_PANEL_ORIGIN.y,
64,
432,
)
// 3. Tab 0 (Fire Spells, Bottom Tab): slice sx = 128
vi.clearAllMocks()
panel.activeTab = 0
panel.draw(mockCtx, {
borderRightImg: null,
skillPointsImg: mockSpImg,
buySellBtnImg: null,
skillTabsImg: mockTabsImg,
}, mockFont)
expect(mockCtx.drawImage).toHaveBeenCalledWith(
mockTabsImg,
128, // sx = 128
0,
64,
432,
SKILL_PANEL_ORIGIN.x + 256,
SKILL_PANEL_ORIGIN.y,
64,
432,
)
})
it('draws authentic skillpoints.png banner when unspentSkillPoints > 0', () => {
const panel = new SkillTreePanel()
panel.visible = true
panel.unspentSkillPoints = 5
const mockCtx = {
save: vi.fn(),
restore: vi.fn(),
drawImage: vi.fn(),
fillRect: vi.fn(),
strokeRect: vi.fn(),
fillStyle: '',
strokeStyle: '',
lineWidth: 1,
globalAlpha: 1,
} as unknown as CanvasRenderingContext2D
const mockFont = {
drawText: vi.fn(),
measureText: vi.fn(() => 50),
} as unknown as D2FontRenderer
const mockSpImg = {
complete: true,
naturalWidth: 135,
naturalHeight: 23,
} as unknown as HTMLImageElement
panel.draw(mockCtx, {
borderRightImg: null,
skillPointsImg: mockSpImg,
buySellBtnImg: null,
}, mockFont)
// Verify skillpoints banner drawn at (ox + 60, oy + 395)
expect(mockCtx.drawImage).toHaveBeenCalledWith(
mockSpImg,
SKILL_PANEL_ORIGIN.x + Math.round((256 - 135) / 2),
SKILL_PANEL_ORIGIN.y + 395,
135,
23,
)
// Verify when unspentSkillPoints = 0, banner is NOT drawn
vi.clearAllMocks()
panel.unspentSkillPoints = 0
panel.draw(mockCtx, {
borderRightImg: null,
skillPointsImg: mockSpImg,
buySellBtnImg: null,
}, mockFont)
expect(mockCtx.drawImage).not.toHaveBeenCalledWith(
mockSpImg,
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
)
})
})