mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
test: 增强测试覆盖(188→226 tests)
- battle.test.ts: 新增 createBattle/executeTurn/settleBattle 边界测试 - EV total cap、非参与者不变、空 participantIds 回退 - applyMoveLearn PP 验证、applyEvolution friendship cap - 多次进化计数器、battlesWon/battlesLost 互斥 - 修复 makeTestCreature friendship 覆盖 - creature.test.ts: 新增 recalculateLevel、getActiveCreature、nature 效果测试 - experience.test.ts: 新增 xpToNextLevel、awardXP 0值、累积验证 - storage.test.ts: 新增 Bag/Box/Release 操作边界测试 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@ function makeTestCreature(overrides: Partial<Creature> = {}): Creature {
|
||||
],
|
||||
ability: overrides.ability ?? 'blaze',
|
||||
heldItem: null,
|
||||
friendship: 70,
|
||||
friendship: overrides.friendship ?? 70,
|
||||
isShiny: false,
|
||||
hatchedAt: Date.now(),
|
||||
pokeball: 'pokeball',
|
||||
@@ -302,3 +302,168 @@ describe('settleBattle - advanced', () => {
|
||||
expect(settlement.data.stats.battlesWon).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createBattle - extended', () => {
|
||||
test('battle state has turn initialized', () => {
|
||||
const creature = makeTestCreature()
|
||||
const init = createBattle([creature], 'squirtle', 50)
|
||||
expect(init.state.turn).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
test('player pokemon has correct level', () => {
|
||||
const creature = makeTestCreature({ level: 25 })
|
||||
const init = createBattle([creature], 'bulbasaur', 10)
|
||||
expect(init.state.playerPokemon.level).toBe(25)
|
||||
})
|
||||
|
||||
test('opponent pokemon has correct level', () => {
|
||||
const creature = makeTestCreature()
|
||||
const init = createBattle([creature], 'squirtle', 15)
|
||||
expect(init.state.opponentPokemon.level).toBe(15)
|
||||
})
|
||||
|
||||
test('battle state has player party', () => {
|
||||
const creature = makeTestCreature()
|
||||
const init = createBattle([creature], 'squirtle', 50)
|
||||
expect(init.state.playerParty.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('battle state has usable items (empty bag)', () => {
|
||||
const creature = makeTestCreature()
|
||||
const init = createBattle([creature], 'squirtle', 50)
|
||||
expect(init.state.usableItems).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('executeTurn - extended', () => {
|
||||
test('item action defaults to move 1', () => {
|
||||
const creature = makeTestCreature()
|
||||
const init = createBattle([creature], 'squirtle', 50)
|
||||
const state = executeTurn(init, { type: 'item', itemId: 'potion' })
|
||||
expect(state).toBeDefined()
|
||||
expect(state.events.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('battle produces damage or heal events', () => {
|
||||
const creature = makeTestCreature({ level: 100, ev: { hp: 252, attack: 252, defense: 0, spAtk: 0, spDef: 4, speed: 252 } })
|
||||
const init = createBattle([creature], 'squirtle', 5)
|
||||
const state = executeTurn(init, { type: 'move', moveIndex: 0 })
|
||||
const hasDamageOrHeal = state.events.some(e => e.type === 'damage' || e.type === 'heal')
|
||||
expect(hasDamageOrHeal).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('settleBattle - EV limits', () => {
|
||||
test('EV total cannot exceed 510', async () => {
|
||||
const creature = makeTestCreature({
|
||||
level: 5,
|
||||
ev: { hp: 250, attack: 250, defense: 10, spAtk: 0, spDef: 0, speed: 0 },
|
||||
})
|
||||
const data = makeTestBuddyData([creature])
|
||||
const result = {
|
||||
winner: 'player' as const,
|
||||
turns: 3,
|
||||
xpGained: 0,
|
||||
evGained: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 },
|
||||
participantIds: [creature.id],
|
||||
}
|
||||
const settlement = await settleBattle(data, result, 'squirtle', 20)
|
||||
const totalEV = Object.values(settlement.data.creatures[0]!.ev).reduce((a, b) => a + b, 0)
|
||||
expect(totalEV).toBeLessThanOrEqual(510)
|
||||
})
|
||||
|
||||
test('non-participant creatures are unchanged', async () => {
|
||||
const participant = makeTestCreature({ id: 'p1', level: 5 })
|
||||
const bystander = makeTestCreature({ id: 'p2', level: 5, speciesId: 'bulbasaur' })
|
||||
const data = makeTestBuddyData([participant, bystander])
|
||||
data.party = [participant.id, bystander.id, null, null, null, null]
|
||||
const result = {
|
||||
winner: 'player' as const,
|
||||
turns: 3,
|
||||
xpGained: 0,
|
||||
evGained: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 },
|
||||
participantIds: [participant.id],
|
||||
}
|
||||
const settlement = await settleBattle(data, result, 'squirtle', 20)
|
||||
const bystanderAfter = settlement.data.creatures.find(c => c.id === 'p2')!
|
||||
expect(bystanderAfter.totalXp).toBe(bystander.totalXp)
|
||||
})
|
||||
|
||||
test('uses all party members as participants when participantIds is empty', async () => {
|
||||
const c1 = makeTestCreature({ id: 'p1', level: 5 })
|
||||
const c2 = makeTestCreature({ id: 'p2', level: 5, speciesId: 'bulbasaur' })
|
||||
const data = makeTestBuddyData([c1, c2])
|
||||
data.party = [c1.id, c2.id, null, null, null, null]
|
||||
const result = {
|
||||
winner: 'player' as const,
|
||||
turns: 3,
|
||||
xpGained: 0,
|
||||
evGained: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 },
|
||||
participantIds: [] as string[],
|
||||
}
|
||||
const settlement = await settleBattle(data, result, 'squirtle', 20)
|
||||
expect(settlement.data.creatures.find(c => c.id === 'p1')!.totalXp).toBeGreaterThan(0)
|
||||
expect(settlement.data.creatures.find(c => c.id === 'p2')!.totalXp).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('player win increments battlesWon but not battlesLost', async () => {
|
||||
const creature = makeTestCreature({ level: 5 })
|
||||
const data = makeTestBuddyData([creature])
|
||||
const result = {
|
||||
winner: 'player' as const,
|
||||
turns: 3,
|
||||
xpGained: 0,
|
||||
evGained: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 },
|
||||
participantIds: [creature.id],
|
||||
}
|
||||
const settlement = await settleBattle(data, result, 'squirtle', 20)
|
||||
expect(settlement.data.stats.battlesWon).toBe(1)
|
||||
expect(settlement.data.stats.battlesLost).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyMoveLearn - extended', () => {
|
||||
test('new move has correct PP from Dex', () => {
|
||||
const creature = makeTestCreature()
|
||||
const data = makeTestBuddyData([creature])
|
||||
const updated = applyMoveLearn(data, creature.id, 'fireblast', 0)
|
||||
const move = updated.creatures[0]!.moves[0]!
|
||||
expect(move.id).toBe('fireblast')
|
||||
expect(move.pp).toBeGreaterThan(0)
|
||||
expect(move.maxPp).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('non-target creatures are unchanged', () => {
|
||||
const c1 = makeTestCreature({ id: 't1' })
|
||||
const c2 = makeTestCreature({ id: 't2', speciesId: 'bulbasaur' })
|
||||
const data = makeTestBuddyData([c1, c2])
|
||||
const updated = applyMoveLearn(data, 't1', 'fireblast', 0)
|
||||
const unchanged = updated.creatures.find(c => c.id === 't2')!
|
||||
expect(unchanged.moves[0]!.id).toBe('flamethrower')
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyEvolution - extended', () => {
|
||||
test('friendship increases by 10', () => {
|
||||
const creature = makeTestCreature({ speciesId: 'charmander', friendship: 70 })
|
||||
const data = makeTestBuddyData([creature])
|
||||
const updated = applyEvolution(data, creature.id, 'charmeleon')
|
||||
expect(updated.creatures[0]!.friendship).toBe(80)
|
||||
})
|
||||
|
||||
test('friendship capped at 255', () => {
|
||||
const creature = makeTestCreature({ speciesId: 'charmander', friendship: 250 })
|
||||
const data = makeTestBuddyData([creature])
|
||||
const updated = applyEvolution(data, creature.id, 'charmeleon')
|
||||
expect(updated.creatures[0]!.friendship).toBe(255)
|
||||
})
|
||||
|
||||
test('multiple evolutions increment counter correctly', () => {
|
||||
const c1 = makeTestCreature({ id: 't1', speciesId: 'charmander' })
|
||||
const c2 = makeTestCreature({ id: 't2', speciesId: 'bulbasaur' })
|
||||
const data = makeTestBuddyData([c1, c2])
|
||||
let updated = applyEvolution(data, 't1', 'charmeleon')
|
||||
updated = applyEvolution(updated, 't2', 'ivysaur')
|
||||
expect(updated.stats.totalEvolutions).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import { generateCreature } from '../core/creature'
|
||||
import { awardXP, getXpProgress } from '../core/experience'
|
||||
import { xpForLevel, levelFromXp } from '../data/xpTable'
|
||||
import { xpForLevel, levelFromXp, xpToNextLevel } from '../data/xpTable'
|
||||
|
||||
describe('xpForLevel', () => {
|
||||
test('level 1 requires 0 XP', () => {
|
||||
@@ -81,4 +81,73 @@ describe('getXpProgress', () => {
|
||||
expect(progress.current).toBe(0)
|
||||
expect(progress.percentage).toBe(0)
|
||||
})
|
||||
|
||||
test('level 100 creature has 100% progress', async () => {
|
||||
const c = await generateCreature('charmander')
|
||||
c.level = 100
|
||||
c.totalXp = 1000000
|
||||
const progress = getXpProgress(c)
|
||||
expect(progress.percentage).toBe(100)
|
||||
})
|
||||
|
||||
test('needed is positive for sub-100 creatures', async () => {
|
||||
const c = await generateCreature('bulbasaur')
|
||||
c.level = 5
|
||||
c.totalXp = xpForLevel(5, 'medium-slow')
|
||||
const progress = getXpProgress(c)
|
||||
expect(progress.needed).toBeGreaterThan(0)
|
||||
expect(progress.current).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('xpToNextLevel', () => {
|
||||
test('returns XP needed from current to next level', () => {
|
||||
const xp10 = xpForLevel(10, 'medium-fast')
|
||||
const xp11 = xpForLevel(11, 'medium-fast')
|
||||
const needed = xpToNextLevel(10, xp10, 'medium-fast')
|
||||
expect(needed).toBe(xp11 - xp10)
|
||||
})
|
||||
|
||||
test('returns 0 at level 100', () => {
|
||||
expect(xpToNextLevel(100, 1000000, 'medium-fast')).toBe(0)
|
||||
})
|
||||
|
||||
test('accounts for partial XP already earned', () => {
|
||||
const xp10 = xpForLevel(10, 'medium-fast')
|
||||
const xp11 = xpForLevel(11, 'medium-fast')
|
||||
const halfWay = xp10 + Math.floor((xp11 - xp10) / 2)
|
||||
const needed = xpToNextLevel(10, halfWay, 'medium-fast')
|
||||
expect(needed).toBe(xp11 - halfWay)
|
||||
})
|
||||
})
|
||||
|
||||
describe('awardXP - extended', () => {
|
||||
test('awarding 0 XP returns unchanged creature', async () => {
|
||||
const c = await generateCreature('bulbasaur')
|
||||
const result = awardXP(c, 0)
|
||||
expect(result.creature.totalXp).toBe(c.totalXp)
|
||||
expect(result.leveledUp).toBe(false)
|
||||
})
|
||||
|
||||
test('XP progress is correctly calculated after award', async () => {
|
||||
const c = await generateCreature('squirtle')
|
||||
const xpNeeded = xpForLevel(2, 'medium-slow')
|
||||
const result = awardXP(c, Math.floor(xpNeeded / 2))
|
||||
expect(result.creature.xp).toBeGreaterThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('multiple small XP awards equal one large award', async () => {
|
||||
const c1 = await generateCreature('bulbasaur', 42)
|
||||
const c2 = await generateCreature('bulbasaur', 42)
|
||||
c2.totalXp = c1.totalXp
|
||||
|
||||
let current = c1
|
||||
for (let i = 0; i < 10; i++) {
|
||||
current = awardXP(current, 100).creature
|
||||
}
|
||||
const bigResult = awardXP(c2, 1000)
|
||||
|
||||
expect(current.totalXp).toBe(bigResult.creature.totalXp)
|
||||
expect(current.level).toBe(bigResult.creature.level)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -325,3 +325,56 @@ describe('incrementTurns', () => {
|
||||
expect(updated.stats.totalTurns).toBe(data.stats.totalTurns + 1)
|
||||
})
|
||||
})
|
||||
|
||||
// ─── Extended coverage ───
|
||||
|
||||
describe('depositToBox - full boxes', () => {
|
||||
test('fails when all boxes are full', () => {
|
||||
const data = makeData()
|
||||
for (const box of data.boxes) {
|
||||
for (let i = 0; i < 30; i++) {
|
||||
box.slots[i] = `filler-${i}`
|
||||
}
|
||||
}
|
||||
const result = depositToBox(data, 'test-id')
|
||||
expect(result.deposited).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('withdrawFromBox - roundtrip', () => {
|
||||
test('deposit then withdraw leaves box empty', () => {
|
||||
const data = makeData()
|
||||
const deposited = depositToBox(data, 'test-id')
|
||||
expect(deposited.deposited).toBe(true)
|
||||
const result = withdrawFromBox(deposited.data, 'test-id')
|
||||
expect(result.withdrawn).toBe(true)
|
||||
const slot = result.data.boxes[0]!.slots.find(s => s === 'test-id')
|
||||
expect(slot).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('findCreatureLocation - deposit', () => {
|
||||
test('finds creature after depositing to box', () => {
|
||||
const data = makeData()
|
||||
const deposited = depositToBox(data, 'box-mon')
|
||||
const loc = findCreatureLocation(deposited.data, 'box-mon')
|
||||
expect(loc).not.toBeNull()
|
||||
expect(loc!.area).toBe('box')
|
||||
})
|
||||
})
|
||||
|
||||
describe('releaseCreature - box', () => {
|
||||
test('removes creature from box and creatures array', () => {
|
||||
const data = makeData()
|
||||
const deposited = depositToBox(data, 'box-mon')
|
||||
const released = releaseCreature(deposited.data, 'box-mon')
|
||||
expect(released.creatures.find(c => c.id === 'box-mon')).toBeUndefined()
|
||||
})
|
||||
|
||||
test('clears party slot when releasing party member', () => {
|
||||
const data = makeData(2)
|
||||
const updated = releaseCreature(data, 'creature-1')
|
||||
expect(updated.party[1]).toBeNull()
|
||||
expect(updated.creatures.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user