288 lines
9.9 KiB
TypeScript
288 lines
9.9 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
formatItemTooltip,
|
|
GEM_INFO,
|
|
RUNE_INFO,
|
|
getSocketableInfo,
|
|
isGemCode,
|
|
isRuneCode,
|
|
} from '../src/game/item-tooltip.ts'
|
|
import type { Item } from '../src/game/items.ts'
|
|
|
|
describe('Diablo II 1.13c Gems & Runes Authentic Display Parity', () => {
|
|
describe('1. Perfect Skull (skz) Screenshot Parity', () => {
|
|
const perfectSkullItem: Item = {
|
|
name: 'Perfect Skull',
|
|
code: 'skz',
|
|
base: { id: 'skz', name: 'Perfect Skull', kind: 'misc' } as any,
|
|
prefix: null,
|
|
suffix: null,
|
|
level: 18,
|
|
ilvl: 18,
|
|
stats: {},
|
|
invWidth: 1,
|
|
invHeight: 1,
|
|
stack: 1,
|
|
value: 1000,
|
|
}
|
|
|
|
it('1.1 English format strictly matches screenshot structure and text', () => {
|
|
const tt = formatItemTooltip(perfectSkullItem, 'en')
|
|
|
|
// Title & Quality
|
|
expect(tt.title).toBe('Perfect Skull')
|
|
expect(tt.subTitle).toBeUndefined()
|
|
expect(tt.quality).toBe('Gem')
|
|
expect(tt.qualityColor).toBe('#ffffff')
|
|
expect(tt.baseType).toBe('Gem')
|
|
expect(tt.isSocketable).toBe(true)
|
|
|
|
// 6 Lines total: Instruction + 4 slots + Required Level
|
|
expect(tt.lines).toHaveLength(6)
|
|
|
|
// Line 0: Instruction
|
|
expect(tt.lines[0]).toEqual({
|
|
text: 'Can be Inserted into Socketed Items',
|
|
color: 'instruction',
|
|
})
|
|
|
|
// Lines 1..4: The 4 distinct slots in pure white
|
|
expect(tt.lines[1]).toEqual({
|
|
text: 'Weapons: 4% Life Stolen Per Hit, 3% Mana Stolen Per Hit',
|
|
color: 'white',
|
|
})
|
|
expect(tt.lines[2]).toEqual({
|
|
text: 'Armor: Regenerate Mana 19%, Replenish Life +5',
|
|
color: 'white',
|
|
})
|
|
expect(tt.lines[3]).toEqual({
|
|
text: 'Helms: Regenerate Mana 19%, Replenish Life +5',
|
|
color: 'white',
|
|
})
|
|
expect(tt.lines[4]).toEqual({
|
|
text: 'Shields: Attacker Takes Damage of 20',
|
|
color: 'white',
|
|
})
|
|
|
|
// Line 5: Required Level in Red
|
|
expect(tt.lines[5]).toEqual({
|
|
text: 'Required Level: 18',
|
|
color: 'red',
|
|
})
|
|
expect(tt.reqLevel).toBe(18)
|
|
})
|
|
|
|
it('1.2 Simplified Chinese format has authentic terminology and red required level', () => {
|
|
const tt = formatItemTooltip(perfectSkullItem, 'zh')
|
|
|
|
expect(tt.title).toBe('完美的骷髅')
|
|
expect(tt.subTitle).toBeUndefined()
|
|
expect(tt.quality).toBe('宝石')
|
|
expect(tt.qualityColor).toBe('#ffffff')
|
|
expect(tt.baseType).toBe('宝石')
|
|
expect(tt.isSocketable).toBe(true)
|
|
|
|
expect(tt.lines).toHaveLength(6)
|
|
expect(tt.lines[0]?.text).toBe('可镶嵌进有凹槽的装备')
|
|
expect(tt.lines[1]).toEqual({
|
|
text: '武器: 每次命中偷取 4% 生命, 每次命中偷取 3% 法力',
|
|
color: 'white',
|
|
})
|
|
expect(tt.lines[2]).toEqual({
|
|
text: '防具: 法力恢复速度 19%, 生命恢复 +5',
|
|
color: 'white',
|
|
})
|
|
expect(tt.lines[3]).toEqual({
|
|
text: '头盔: 法力恢复速度 19%, 生命恢复 +5',
|
|
color: 'white',
|
|
})
|
|
expect(tt.lines[4]).toEqual({
|
|
text: '盾牌: 攻击者受到伤害 20',
|
|
color: 'white',
|
|
})
|
|
expect(tt.lines[5]).toEqual({
|
|
text: '需要等级: 18',
|
|
color: 'red',
|
|
})
|
|
})
|
|
|
|
it('1.3 Bilingual format includes both English and Chinese names', () => {
|
|
const tt = formatItemTooltip(perfectSkullItem, 'bilingual')
|
|
expect(tt.title).toBe('Perfect Skull (完美的骷髅)')
|
|
expect(tt.lines[0]?.text).toBe('可镶嵌进有凹槽的装备')
|
|
expect(tt.lines[5]?.text).toBe('需要等级: 18')
|
|
})
|
|
})
|
|
|
|
describe('2. Full Spectrum 35 Gems Verification', () => {
|
|
it('2.1 Exactly 35 gems exist across 7 types and 5 tiers', () => {
|
|
const codes = Object.keys(GEM_INFO)
|
|
expect(codes).toHaveLength(35)
|
|
})
|
|
|
|
it('2.2 Every gem formats correctly with 4 distinct slots and red reqLevel', () => {
|
|
for (const [code, info] of Object.entries(GEM_INFO)) {
|
|
const item: Item = {
|
|
name: info.nameEn,
|
|
code,
|
|
base: { id: code, name: info.nameEn, kind: 'misc' } as any,
|
|
prefix: null,
|
|
suffix: null,
|
|
level: info.reqLevel,
|
|
ilvl: info.reqLevel,
|
|
stats: {},
|
|
invWidth: 1,
|
|
invHeight: 1,
|
|
stack: 1,
|
|
value: 100,
|
|
}
|
|
|
|
const ttEn = formatItemTooltip(item, 'en')
|
|
expect(ttEn.title).toBe(info.nameEn)
|
|
expect(ttEn.qualityColor).toBe('#ffffff')
|
|
expect(ttEn.isSocketable).toBe(true)
|
|
expect(ttEn.lines[0]?.text).toBe('Can be Inserted into Socketed Items')
|
|
expect(ttEn.lines[1]?.text).toBe(`Weapons: ${info.weaponsEn}`)
|
|
expect(ttEn.lines[2]?.text).toBe(`Armor: ${info.armorEn}`)
|
|
expect(ttEn.lines[3]?.text).toBe(`Helms: ${info.helmsEn}`)
|
|
expect(ttEn.lines[4]?.text).toBe(`Shields: ${info.shieldsEn}`)
|
|
expect(ttEn.lines[5]?.text).toBe(`Required Level: ${info.reqLevel}`)
|
|
expect(ttEn.lines[5]?.color).toBe('red')
|
|
|
|
const ttZh = formatItemTooltip(item, 'zh')
|
|
expect(ttZh.title).toBe(info.nameZh)
|
|
expect(ttZh.lines[0]?.text).toBe('可镶嵌进有凹槽的装备')
|
|
expect(ttZh.lines[1]?.text).toBe(`武器: ${info.weaponsZh}`)
|
|
expect(ttZh.lines[2]?.text).toBe(`防具: ${info.armorZh}`)
|
|
expect(ttZh.lines[3]?.text).toBe(`头盔: ${info.helmsZh}`)
|
|
expect(ttZh.lines[4]?.text).toBe(`盾牌: ${info.shieldsZh}`)
|
|
expect(ttZh.lines[5]?.text).toBe(`需要等级: ${info.reqLevel}`)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('3. Full Spectrum 33 Runes Verification', () => {
|
|
it('3.1 Exactly 33 runes exist (r01..r33)', () => {
|
|
const runeKeys = Object.keys(RUNE_INFO)
|
|
expect(runeKeys).toHaveLength(33)
|
|
for (let i = 1; i <= 33; i++) {
|
|
const key = `r${String(i).padStart(2, '0')}`
|
|
expect(RUNE_INFO[key]).toBeDefined()
|
|
expect(RUNE_INFO[key]?.num).toBe(i)
|
|
}
|
|
})
|
|
|
|
it('3.2 Hel Rune (r15) reqLevel=0 suppresses Required Level line', () => {
|
|
const helItem: Item = {
|
|
name: 'Hel Rune',
|
|
code: 'r15',
|
|
base: { id: 'r15', name: 'Hel Rune', kind: 'misc' } as any,
|
|
prefix: null,
|
|
suffix: null,
|
|
level: 1,
|
|
ilvl: 1,
|
|
stats: {},
|
|
invWidth: 1,
|
|
invHeight: 1,
|
|
stack: 1,
|
|
value: 100,
|
|
}
|
|
const tt = formatItemTooltip(helItem, 'en')
|
|
expect(tt.title).toBe('Hel Rune')
|
|
expect(tt.reqLevel).toBeUndefined()
|
|
// Exactly 5 lines: instruction + 4 slots (no Required Level line)
|
|
expect(tt.lines).toHaveLength(5)
|
|
expect(tt.lines.some(l => l.text.includes('Required Level'))).toBe(false)
|
|
})
|
|
|
|
it('3.3 Ber Rune (r30) formats with orange title and correct 4 slots', () => {
|
|
const berItem: Item = {
|
|
name: 'Ber Rune',
|
|
code: 'r30',
|
|
base: { id: 'r30', name: 'Ber Rune', kind: 'misc' } as any,
|
|
prefix: null,
|
|
suffix: null,
|
|
level: 63,
|
|
ilvl: 63,
|
|
stats: {},
|
|
invWidth: 1,
|
|
invHeight: 1,
|
|
stack: 1,
|
|
value: 100,
|
|
}
|
|
const ttEn = formatItemTooltip(berItem, 'en')
|
|
expect(ttEn.title).toBe('Ber Rune')
|
|
expect(ttEn.subTitle).toBe('Rune (30)')
|
|
expect(ttEn.qualityColor).toBe('#ff8c00')
|
|
expect(ttEn.lines[1]?.text).toBe('Weapons: 20% Chance of Crushing Blow')
|
|
expect(ttEn.lines[2]?.text).toBe('Armor: Damage Reduced by 8%')
|
|
expect(ttEn.lines[3]?.text).toBe('Helms: Damage Reduced by 8%')
|
|
expect(ttEn.lines[4]?.text).toBe('Shields: Damage Reduced by 8%')
|
|
expect(ttEn.lines[5]?.text).toBe('Required Level: 63')
|
|
expect(ttEn.lines[5]?.color).toBe('red')
|
|
|
|
const ttZh = formatItemTooltip(berItem, 'zh')
|
|
expect(ttZh.title).toBe('贝符文')
|
|
expect(ttZh.subTitle).toBe('符文 (30)')
|
|
expect(ttZh.qualityColor).toBe('#ff8c00')
|
|
expect(ttZh.lines[1]?.text).toBe('武器: 20% 几率造成压碎打击')
|
|
expect(ttZh.lines[2]?.text).toBe('防具: 伤害减少 8%')
|
|
expect(ttZh.lines[3]?.text).toBe('头盔: 伤害减少 8%')
|
|
expect(ttZh.lines[4]?.text).toBe('盾牌: 伤害减少 8%')
|
|
expect(ttZh.lines[5]?.text).toBe('需要等级: 63')
|
|
})
|
|
|
|
it('3.4 Jah Rune (r31) correctly differentiates Armor/Helms (+5% max life) vs Shields (+50 life)', () => {
|
|
const jahItem: Item = {
|
|
name: 'Jah Rune',
|
|
code: 'r31',
|
|
base: { id: 'r31', name: 'Jah Rune', kind: 'misc' } as any,
|
|
prefix: null,
|
|
suffix: null,
|
|
level: 65,
|
|
ilvl: 65,
|
|
stats: {},
|
|
invWidth: 1,
|
|
invHeight: 1,
|
|
stack: 1,
|
|
value: 100,
|
|
}
|
|
const tt = formatItemTooltip(jahItem, 'en')
|
|
expect(tt.lines[1]?.text).toBe("Weapons: Ignore Target's Defense")
|
|
expect(tt.lines[2]?.text).toBe('Armor: Increase Maximum Life 5%')
|
|
expect(tt.lines[3]?.text).toBe('Helms: Increase Maximum Life 5%')
|
|
expect(tt.lines[4]?.text).toBe('Shields: +50 to Life')
|
|
})
|
|
})
|
|
|
|
describe('4. Helper functions parity', () => {
|
|
it('4.1 isGemCode identifies all gems and rejects non-gems', () => {
|
|
expect(isGemCode('skz')).toBe(true)
|
|
expect(isGemCode('gcv')).toBe(true)
|
|
expect(isGemCode('gpw')).toBe(true)
|
|
expect(isGemCode('r01')).toBe(false)
|
|
expect(isGemCode('hax')).toBe(false)
|
|
expect(isGemCode(undefined)).toBe(false)
|
|
})
|
|
|
|
it('4.2 isRuneCode identifies all runes and rejects non-runes', () => {
|
|
expect(isRuneCode('r01')).toBe(true)
|
|
expect(isRuneCode('r33')).toBe(true)
|
|
expect(isRuneCode('skz')).toBe(false)
|
|
expect(isRuneCode('r00')).toBe(false)
|
|
expect(isRuneCode('r34')).toBe(false)
|
|
expect(isRuneCode(undefined)).toBe(false)
|
|
})
|
|
|
|
it('4.3 getSocketableInfo returns valid info for both gems and runes', () => {
|
|
const skz = getSocketableInfo('skz')
|
|
expect(skz?.nameEn).toBe('Perfect Skull')
|
|
|
|
const r30 = getSocketableInfo('r30')
|
|
expect(r30?.nameEn).toBe('Ber Rune')
|
|
|
|
expect(getSocketableInfo('invalid')).toBeUndefined()
|
|
})
|
|
})
|
|
})
|