Features: - Combat system with AP/EP hit calculation and three-layer defense - Auto-combat/farming mode - Item system with stacking support - Skill system with levels, milestones, and parent skill sync - Shop system with dynamic pricing - Inventory management with bulk selling - Event system - Game loop with offline earnings - Save/Load system Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
384 lines
11 KiB
JavaScript
384 lines
11 KiB
JavaScript
/**
|
|
* 物品系统单元测试
|
|
* 测试纯函数,不依赖框架
|
|
*/
|
|
|
|
describe('物品系统 - 纯函数测试', () => {
|
|
describe('getQualityLevel - 品质等级计算', () => {
|
|
const QUALITY_LEVELS = {
|
|
1: { level: 1, name: '垃圾', color: '#9e9e9e', range: [0, 49], multiplier: 0.5 },
|
|
2: { level: 2, name: '普通', color: '#ffffff', range: [50, 99], multiplier: 1.0 },
|
|
3: { level: 3, name: '优秀', color: '#1eff00', range: [100, 129], multiplier: 1.3 },
|
|
4: { level: 4, name: '稀有', color: '#0070dd', range: [130, 159], multiplier: 1.6 },
|
|
5: { level: 5, name: '史诗', color: '#a335ee', range: [160, 199], multiplier: 2.0 },
|
|
6: { level: 6, name: '传说', color: '#ff8000', range: [200, 250], multiplier: 2.5 }
|
|
}
|
|
|
|
const getQualityLevel = (quality) => {
|
|
const clampedQuality = Math.max(0, Math.min(250, quality))
|
|
|
|
for (const [level, data] of Object.entries(QUALITY_LEVELS)) {
|
|
if (clampedQuality >= data.range[0] && clampedQuality <= data.range[1]) {
|
|
return {
|
|
level: parseInt(level),
|
|
...data
|
|
}
|
|
}
|
|
}
|
|
|
|
return QUALITY_LEVELS[1]
|
|
}
|
|
|
|
test('应该正确识别垃圾品质', () => {
|
|
const level = getQualityLevel(0)
|
|
expect(level.level).toBe(1)
|
|
expect(level.name).toBe('垃圾')
|
|
expect(level.multiplier).toBe(0.5)
|
|
})
|
|
|
|
test('应该正确识别普通品质', () => {
|
|
const level = getQualityLevel(75)
|
|
expect(level.level).toBe(2)
|
|
expect(level.name).toBe('普通')
|
|
expect(level.multiplier).toBe(1.0)
|
|
})
|
|
|
|
test('应该正确识别优秀品质', () => {
|
|
const level = getQualityLevel(115)
|
|
expect(level.level).toBe(3)
|
|
expect(level.name).toBe('优秀')
|
|
expect(level.multiplier).toBe(1.3)
|
|
})
|
|
|
|
test('应该正确识别稀有品质', () => {
|
|
const level = getQualityLevel(145)
|
|
expect(level.level).toBe(4)
|
|
expect(level.name).toBe('稀有')
|
|
expect(level.multiplier).toBe(1.6)
|
|
})
|
|
|
|
test('应该正确识别史诗品质', () => {
|
|
const level = getQualityLevel(180)
|
|
expect(level.level).toBe(5)
|
|
expect(level.name).toBe('史诗')
|
|
expect(level.multiplier).toBe(2.0)
|
|
})
|
|
|
|
test('应该正确识别传说品质', () => {
|
|
const level = getQualityLevel(220)
|
|
expect(level.level).toBe(6)
|
|
expect(level.name).toBe('传说')
|
|
expect(level.multiplier).toBe(2.5)
|
|
})
|
|
|
|
test('品质边界值测试', () => {
|
|
expect(getQualityLevel(49).level).toBe(1) // 垃圾上限
|
|
expect(getQualityLevel(50).level).toBe(2) // 普通下限
|
|
expect(getQualityLevel(99).level).toBe(2) // 普通上限
|
|
expect(getQualityLevel(100).level).toBe(3) // 优秀下限
|
|
expect(getQualityLevel(250).level).toBe(6) // 传说上限
|
|
})
|
|
|
|
test('超出范围应该被限制', () => {
|
|
expect(getQualityLevel(-10).level).toBe(1) // 负数
|
|
expect(getQualityLevel(300).level).toBe(6) // 超过250
|
|
})
|
|
})
|
|
|
|
describe('calculateItemStats - 物品属性计算', () => {
|
|
test('应该正确计算武器伤害', () => {
|
|
const baseDamage = 10
|
|
const quality = 100
|
|
const qualityMultiplier = 1.3
|
|
const qualityRatio = quality / 100
|
|
|
|
const finalDamage = Math.floor(baseDamage * qualityRatio * qualityMultiplier)
|
|
|
|
expect(finalDamage).toBe(13) // 10 * 1.0 * 1.3 = 13
|
|
})
|
|
|
|
test('应该正确计算防御力', () => {
|
|
const baseDefense = 20
|
|
const quality = 150
|
|
const qualityMultiplier = 1.6
|
|
const qualityRatio = quality / 100
|
|
|
|
const finalDefense = Math.floor(baseDefense * qualityRatio * qualityMultiplier)
|
|
|
|
expect(finalDefense).toBe(48) // 20 * 1.5 * 1.6 = 48
|
|
})
|
|
|
|
test('应该正确计算物品价值', () => {
|
|
const baseValue = 100
|
|
const quality = 120
|
|
const qualityMultiplier = 1.3
|
|
const qualityRatio = quality / 100
|
|
|
|
const finalValue = Math.floor(baseValue * qualityRatio * qualityMultiplier)
|
|
|
|
expect(finalValue).toBe(156) // 100 * 1.2 * 1.3 = 156
|
|
})
|
|
|
|
test('品质为0时应该有最小值', () => {
|
|
const baseDamage = 10
|
|
const quality = 0
|
|
const qualityMultiplier = 0.5
|
|
const qualityRatio = quality / 100
|
|
|
|
const finalDamage = Math.floor(baseDamage * qualityRatio * qualityMultiplier)
|
|
|
|
expect(finalDamage).toBe(0) // 10 * 0 * 0.5 = 0
|
|
})
|
|
})
|
|
|
|
describe('generateRandomQuality - 随机品质生成', () => {
|
|
test('基础品质范围应该是50-150', () => {
|
|
const baseMin = 50
|
|
const baseMax = 150
|
|
|
|
const randomQuality = baseMin + Math.floor(Math.random() * (baseMax - baseMin + 1))
|
|
|
|
expect(randomQuality).toBeGreaterThanOrEqual(50)
|
|
expect(randomQuality).toBeLessThanOrEqual(150)
|
|
})
|
|
|
|
test('运气应该增加品质', () => {
|
|
const baseQuality = 100
|
|
const luck = 20
|
|
const luckBonus = luck * 0.5
|
|
|
|
const finalQuality = baseQuality + luckBonus
|
|
|
|
expect(finalQuality).toBe(110) // 100 + 20 * 0.5 = 110
|
|
})
|
|
|
|
test('品质应该被限制在0-250之间', () => {
|
|
const clamp = (value) => Math.min(250, Math.max(0, value))
|
|
|
|
expect(clamp(-10)).toBe(0)
|
|
expect(clamp(0)).toBe(0)
|
|
expect(clamp(250)).toBe(250)
|
|
expect(clamp(300)).toBe(250)
|
|
})
|
|
|
|
test('传说品质应该非常稀有', () => {
|
|
// 传说品质概率 < 0.02
|
|
const legendaryChance = 0.01 + 50 / 10000
|
|
|
|
expect(legendaryChance).toBeLessThan(0.02)
|
|
})
|
|
|
|
test('史诗品质应该稀有', () => {
|
|
// 史诗品质概率 < 0.08
|
|
const epicChance = 0.05 + 50 / 2000
|
|
|
|
expect(epicChance).toBeLessThan(0.08)
|
|
})
|
|
})
|
|
|
|
describe('calculateSellPrice - 出售价格计算', () => {
|
|
test('应该正确计算基础出售价格', () => {
|
|
const baseValue = 100
|
|
const sellRate = 0.5 // 50%
|
|
|
|
const sellPrice = Math.floor(baseValue * sellRate)
|
|
|
|
expect(sellPrice).toBe(50)
|
|
})
|
|
|
|
test('品质应该影响出售价格', () => {
|
|
const baseValue = 100
|
|
const quality = 120
|
|
const qualityMultiplier = 1.3
|
|
const sellRate = 0.5
|
|
|
|
const sellPrice = Math.floor(baseValue * (quality / 100) * qualityMultiplier * sellRate)
|
|
|
|
expect(sellPrice).toBe(78) // 100 * 1.2 * 1.3 * 0.5 = 78
|
|
})
|
|
|
|
test('市场倍率应该影响价格', () => {
|
|
const baseValue = 100
|
|
const quality = 100
|
|
const qualityMultiplier = 1.0
|
|
const marketRate = 1.5 // 市场繁荣
|
|
|
|
const sellPrice = Math.floor(baseValue * 1.0 * qualityMultiplier * 0.5 * marketRate)
|
|
|
|
expect(sellPrice).toBe(75) // 100 * 1.0 * 1.0 * 0.5 * 1.5 = 75
|
|
})
|
|
})
|
|
|
|
describe('calculateBuyPrice - 购买价格计算', () => {
|
|
test('应该正确计算基础购买价格', () => {
|
|
const baseValue = 100
|
|
const buyRate = 1.5 // 150%
|
|
|
|
const buyPrice = Math.floor(baseValue * buyRate)
|
|
|
|
expect(buyPrice).toBe(150)
|
|
})
|
|
|
|
test('品质应该影响购买价格', () => {
|
|
const baseValue = 100
|
|
const quality = 150
|
|
const qualityMultiplier = 1.6
|
|
const buyRate = 1.5
|
|
|
|
const buyPrice = Math.floor(baseValue * (quality / 100) * qualityMultiplier * buyRate)
|
|
|
|
expect(buyPrice).toBe(360) // 100 * 1.5 * 1.6 * 1.5 = 360
|
|
})
|
|
})
|
|
|
|
describe('物品堆叠', () => {
|
|
test('可堆叠物品应该有最大堆叠数', () => {
|
|
const stackable = true
|
|
const maxStack = 99
|
|
|
|
const canAddToStack = (currentStack, amount) => {
|
|
return currentStack + amount <= maxStack
|
|
}
|
|
|
|
expect(canAddToStack(50, 40)).toBe(true)
|
|
expect(canAddToStack(50, 49)).toBe(true)
|
|
expect(canAddToStack(50, 50)).toBe(false)
|
|
})
|
|
|
|
test('不可堆叠物品每次只能有一个', () => {
|
|
const stackable = false
|
|
const maxStack = 1
|
|
|
|
const canAddToStack = (currentStack) => {
|
|
return currentStack < maxStack
|
|
}
|
|
|
|
expect(canAddToStack(0)).toBe(true)
|
|
expect(canAddToStack(1)).toBe(false)
|
|
})
|
|
|
|
test('使用堆叠物品应该减少数量', () => {
|
|
const itemCount = 10
|
|
const useCount = 3
|
|
|
|
const remainingCount = itemCount - useCount
|
|
|
|
expect(remainingCount).toBe(7)
|
|
})
|
|
|
|
test('堆叠为0时物品应该被移除', () => {
|
|
const itemCount = 1
|
|
const useCount = 1
|
|
|
|
const shouldRemove = itemCount - useCount <= 0
|
|
|
|
expect(shouldRemove).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('装备物品', () => {
|
|
test('武器应该装备到武器槽', () => {
|
|
const itemType = 'weapon'
|
|
const equipment = {
|
|
weapon: null,
|
|
armor: null
|
|
}
|
|
|
|
const canEquip = (slot) => {
|
|
return equipment[slot] === null
|
|
}
|
|
|
|
expect(canEquip('weapon')).toBe(true)
|
|
expect(canEquip('armor')).toBe(true)
|
|
})
|
|
|
|
test('装备槽已占用时不能装备', () => {
|
|
const equipment = {
|
|
weapon: { id: 'old_sword' }
|
|
}
|
|
|
|
const canEquip = (slot) => {
|
|
return equipment[slot] === null
|
|
}
|
|
|
|
expect(canEquip('weapon')).toBe(false)
|
|
})
|
|
|
|
test('双手武器应该占用两个槽位', () => {
|
|
const item = {
|
|
id: 'two_handed_sword',
|
|
twoHanded: true
|
|
}
|
|
|
|
const slotsNeeded = item.twoHanded ? 2 : 1
|
|
|
|
expect(slotsNeeded).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('消耗品效果', () => {
|
|
test('生命药水应该恢复生命值', () => {
|
|
const currentHealth = 50
|
|
const maxHealth = 100
|
|
const healAmount = 30
|
|
|
|
const newHealth = Math.min(maxHealth, currentHealth + healAmount)
|
|
|
|
expect(newHealth).toBe(80)
|
|
})
|
|
|
|
test('生命值已满时不应该溢出', () => {
|
|
const currentHealth = 100
|
|
const maxHealth = 100
|
|
const healAmount = 30
|
|
|
|
const newHealth = Math.min(maxHealth, currentHealth + healAmount)
|
|
|
|
expect(newHealth).toBe(100)
|
|
})
|
|
|
|
test('耐力药水应该恢复耐力', () => {
|
|
const currentStamina = 20
|
|
const maxStamina = 100
|
|
const restoreAmount = 50
|
|
|
|
const newStamina = Math.min(maxStamina, currentStamina + restoreAmount)
|
|
|
|
expect(newStamina).toBe(70)
|
|
})
|
|
})
|
|
|
|
describe('边界条件', () => {
|
|
test('品质为负数时应该设为0', () => {
|
|
const quality = -10
|
|
const clampedQuality = Math.max(0, quality)
|
|
|
|
expect(clampedQuality).toBe(0)
|
|
})
|
|
|
|
test('品质超过250时应该设为250', () => {
|
|
const quality = 300
|
|
const clampedQuality = Math.min(250, quality)
|
|
|
|
expect(clampedQuality).toBe(250)
|
|
})
|
|
|
|
test('物品价值不能为负数', () => {
|
|
const baseValue = -100
|
|
const finalValue = Math.max(0, baseValue)
|
|
|
|
expect(finalValue).toBe(0)
|
|
})
|
|
|
|
test('伤害不能为负数', () => {
|
|
const baseDamage = -10
|
|
const qualityRatio = 1.0
|
|
const qualityMultiplier = 1.0
|
|
|
|
const finalDamage = Math.max(0, Math.floor(baseDamage * qualityRatio * qualityMultiplier))
|
|
|
|
expect(finalDamage).toBe(0)
|
|
})
|
|
})
|
|
})
|