154 lines
7.1 KiB
TypeScript
154 lines
7.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { execSync } from 'node:child_process'
|
|
import { MISSILE_METAS } from '../src/render/missiles-meta.ts'
|
|
import {
|
|
OVERLAY_METAS,
|
|
LIGHTNING_OVERLAY_META,
|
|
FROZEN_ARMOR_OVERLAY_META,
|
|
FROZEN_ARMOR_HIT_OVERLAY_META,
|
|
ENERGY_SHIELD_OVERLAY_META,
|
|
ENERGY_SHIELD_HIT_OVERLAY_META,
|
|
THUNDERSTORM_CAST_OVERLAY_META,
|
|
THUNDERSTORM_BACK_OVERLAY_META,
|
|
ENCHANT_OVERLAY_META,
|
|
ICE_EXPLODE_OVERLAY_META,
|
|
} from '../src/render/overlays-meta.ts'
|
|
|
|
describe('Milestone M0 — 9 Sorceress Skill & Hit Overlays Parity', () => {
|
|
const publicDir = path.resolve(__dirname, '../public/overlays')
|
|
const packDir = path.resolve(__dirname, '../samples/d2-packs/overlays')
|
|
|
|
const TARGETS = [
|
|
{ name: 'lightning', meta: LIGHTNING_OVERLAY_META, celFile: 'ShockHitSm', frames: 20, trans: 3, preDraw: false },
|
|
{ name: 'frozenarmor', meta: FROZEN_ARMOR_OVERLAY_META, celFile: 'FrozenArmor', frames: 24, trans: 3, preDraw: false },
|
|
{ name: 'frozenarmor_hit', meta: FROZEN_ARMOR_HIT_OVERLAY_META, celFile: 'AuraResistColdCast', frames: 11, trans: 3, preDraw: true },
|
|
{ name: 'energyshield', meta: ENERGY_SHIELD_OVERLAY_META, celFile: 'ManaShieldBall', frames: 8, trans: 5, preDraw: false },
|
|
{ name: 'energyshieldhit0', meta: ENERGY_SHIELD_HIT_OVERLAY_META, celFile: 'ManaShieldDir00', frames: 9, trans: 3, preDraw: false },
|
|
{ name: 'thunderstormcast', meta: THUNDERSTORM_CAST_OVERLAY_META, celFile: 'ThunderstormCast', frames: 10, trans: 3, preDraw: false },
|
|
{ name: 'thunderstormback', meta: THUNDERSTORM_BACK_OVERLAY_META, celFile: 'ThunderstormCast_operate', frames: 19, trans: 3, preDraw: true },
|
|
{ name: 'enchant', meta: ENCHANT_OVERLAY_META, celFile: 'FireEnchant', frames: 17, trans: 3, preDraw: false },
|
|
{ name: 'ice_explode', meta: ICE_EXPLODE_OVERLAY_META, celFile: 'IceExplode', frames: 16, trans: 3, preDraw: false },
|
|
]
|
|
|
|
it('1. Verifies physical PNG and JSON files exist in public/overlays/ and samples/d2-packs/overlays/', () => {
|
|
for (const t of TARGETS) {
|
|
for (const dir of [publicDir, packDir]) {
|
|
const pngPath = path.join(dir, `${t.name}.png`)
|
|
const jsonPath = path.join(dir, `${t.name}.json`)
|
|
expect(fs.existsSync(pngPath), `Missing ${pngPath}`).toBe(true)
|
|
expect(fs.existsSync(jsonPath), `Missing ${jsonPath}`).toBe(true)
|
|
expect(fs.statSync(pngPath).size).toBeGreaterThan(1000)
|
|
|
|
const raw = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'))
|
|
expect(raw.name).toBe(t.name)
|
|
expect(raw.celFile).toBe(t.celFile)
|
|
expect(raw.frames).toBe(t.frames)
|
|
expect(raw.trans).toBe(t.trans)
|
|
expect(raw.preDraw).toBe(t.preDraw)
|
|
}
|
|
}
|
|
})
|
|
|
|
it('2. Verifies typed metadata constants and properties match 1.13c ground truth', () => {
|
|
for (const t of TARGETS) {
|
|
expect(t.meta.name).toBe(t.name)
|
|
expect(t.meta.celFile).toBe(t.celFile)
|
|
expect(t.meta.frames).toBe(t.frames)
|
|
expect(t.meta.framesPerDirection).toBe(t.frames)
|
|
expect(t.meta.directions).toBe(1)
|
|
expect(t.meta.trans).toBe(t.trans)
|
|
expect(t.meta.preDraw).toBe(t.preDraw)
|
|
expect(t.meta.groups[0]?.length).toBe(t.frames)
|
|
expect(t.meta.box.width).toBeGreaterThan(0)
|
|
expect(t.meta.box.height).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
|
|
it('3. Verifies OVERLAY_METAS maps primary keys, celFiles, and aliases cleanly', () => {
|
|
expect(OVERLAY_METAS['lightning']).toBe(LIGHTNING_OVERLAY_META)
|
|
expect(OVERLAY_METAS['ShockHitSm']).toBe(LIGHTNING_OVERLAY_META)
|
|
expect(OVERLAY_METAS['holyshockhit']).toBe(LIGHTNING_OVERLAY_META)
|
|
|
|
expect(OVERLAY_METAS['frozenarmor']).toBe(FROZEN_ARMOR_OVERLAY_META)
|
|
expect(OVERLAY_METAS['shiverarmor']).toBe(FROZEN_ARMOR_OVERLAY_META)
|
|
expect(OVERLAY_METAS['chillarmor']).toBe(FROZEN_ARMOR_OVERLAY_META)
|
|
|
|
expect(OVERLAY_METAS['frozenarmor_hit']).toBe(FROZEN_ARMOR_HIT_OVERLAY_META)
|
|
expect(OVERLAY_METAS['cast_shivers']).toBe(FROZEN_ARMOR_HIT_OVERLAY_META)
|
|
expect(OVERLAY_METAS['shiverarmor_hit']).toBe(FROZEN_ARMOR_HIT_OVERLAY_META)
|
|
|
|
expect(OVERLAY_METAS['energyshield']).toBe(ENERGY_SHIELD_OVERLAY_META)
|
|
expect(OVERLAY_METAS['energyshieldhit0']).toBe(ENERGY_SHIELD_HIT_OVERLAY_META)
|
|
|
|
expect(OVERLAY_METAS['thunderstormcast']).toBe(THUNDERSTORM_CAST_OVERLAY_META)
|
|
expect(OVERLAY_METAS['thunderstormback']).toBe(THUNDERSTORM_BACK_OVERLAY_META)
|
|
|
|
expect(OVERLAY_METAS['enchant']).toBe(ENCHANT_OVERLAY_META)
|
|
expect(OVERLAY_METAS['ice_explode']).toBe(ICE_EXPLODE_OVERLAY_META)
|
|
})
|
|
|
|
it('4. Verifies package.json contains "pack:overlays" script and execution succeeds', () => {
|
|
const pkgPath = path.resolve(__dirname, '../package.json')
|
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
expect(pkg.scripts, 'package.json missing scripts section').toBeDefined()
|
|
expect(pkg.scripts['pack:overlays'], 'package.json missing pack:overlays').toBe('tsx scripts/pack-overlays.ts')
|
|
expect(pkg.scripts['pack:missiles'], 'package.json missing pack:missiles').toBe('tsx scripts/pack-missiles.ts')
|
|
|
|
// Execute npm run pack:overlays and verify zero exit code & console logging
|
|
const output = execSync('npm run pack:overlays', {
|
|
cwd: path.resolve(__dirname, '..'),
|
|
encoding: 'utf-8',
|
|
timeout: 30000,
|
|
})
|
|
expect(output).toContain('[Overlay Packer] Baked')
|
|
expect(output).toContain('overlays-meta.ts')
|
|
})
|
|
|
|
it('5. Verifies CelFile casing lookups in OVERLAY_METAS', () => {
|
|
const CEL_FILE_CASING_TESTS = [
|
|
{ key: 'ShockHitSm', expectedMeta: LIGHTNING_OVERLAY_META },
|
|
{ key: 'FrozenArmor', expectedMeta: FROZEN_ARMOR_OVERLAY_META },
|
|
{ key: 'AuraResistColdCast', expectedMeta: FROZEN_ARMOR_HIT_OVERLAY_META },
|
|
{ key: 'ManaShieldBall', expectedMeta: ENERGY_SHIELD_OVERLAY_META },
|
|
{ key: 'ManaShieldDir00', expectedMeta: ENERGY_SHIELD_HIT_OVERLAY_META },
|
|
{ key: 'ThunderstormCast', expectedMeta: THUNDERSTORM_CAST_OVERLAY_META },
|
|
{ key: 'ThunderstormCast_operate', expectedMeta: THUNDERSTORM_BACK_OVERLAY_META },
|
|
{ key: 'FireEnchant', expectedMeta: ENCHANT_OVERLAY_META },
|
|
{ key: 'IceExplode', expectedMeta: ICE_EXPLODE_OVERLAY_META },
|
|
]
|
|
|
|
for (const { key, expectedMeta } of CEL_FILE_CASING_TESTS) {
|
|
expect(OVERLAY_METAS[key], `OVERLAY_METAS['${key}'] should resolve directly`).toBe(expectedMeta)
|
|
expect(OVERLAY_METAS[key.toLowerCase()], `OVERLAY_METAS['${key.toLowerCase()}'] should resolve`).toBe(expectedMeta)
|
|
}
|
|
})
|
|
|
|
it('6. Verifies CelFile casing lookups in MISSILE_METAS', () => {
|
|
const MISSILE_CASING_KEYS = [
|
|
'ElectricNova',
|
|
'LightningStrike',
|
|
'LightningboltBig',
|
|
'Gleam',
|
|
'FreezeExplodeEjecta',
|
|
'groundFireBig',
|
|
'Flamethrower',
|
|
'Flamethrower2',
|
|
'FrostNova',
|
|
'Firebolt',
|
|
'Fireball',
|
|
'ChargedBolt',
|
|
'Icebolt',
|
|
'IceBlast',
|
|
'GlacialSpike',
|
|
'Blizzard',
|
|
]
|
|
|
|
for (const key of MISSILE_CASING_KEYS) {
|
|
expect(MISSILE_METAS[key], `MISSILE_METAS['${key}'] should resolve directly`).toBeDefined()
|
|
expect(MISSILE_METAS[key.toLowerCase()], `MISSILE_METAS['${key.toLowerCase()}'] should resolve`).toBeDefined()
|
|
}
|
|
})
|
|
})
|