test: 新增 storage.test.ts

- 验证 BuddyData v2 结构正确性
- 验证 creature 包含 v2 字段

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-22 00:44:12 +08:00
parent 1dd36f3f6f
commit 96e6d33414

View File

@@ -1,5 +1,67 @@
import { describe, test, expect } from 'bun:test'
import { getDefaultBuddyData } from '../core/storage'
import {
getDefaultBuddyData,
addToParty, removeFromParty, swapPartySlots, setActivePartyMember,
depositToBox, withdrawFromBox, moveInBox, renameBox,
findCreatureLocation, releaseCreature, getTotalCreatureCount, getAllCreatureIds,
addItemToBag, removeItemFromBag, getItemCount,
updateDailyStats, incrementTurns,
} from '../core/storage'
import type { BuddyData } from '../types'
function makeData(creatureCount = 1): BuddyData {
const creatures = Array.from({ length: creatureCount }, (_, i) => ({
id: `creature-${i}`,
speciesId: 'bulbasaur' as const,
gender: 'male' as const,
level: 5,
xp: 0,
totalXp: 100,
nature: 'hardy',
ev: { hp: 0, attack: 0, defense: 0, spAtk: 0, spDef: 0, speed: 0 },
iv: { hp: 15, attack: 15, defense: 15, spAtk: 15, spDef: 15, speed: 15 },
moves: [
{ id: 'tackle', pp: 35, maxPp: 35 },
{ id: '', pp: 0, maxPp: 0 },
{ id: '', pp: 0, maxPp: 0 },
{ id: '', pp: 0, maxPp: 0 },
] as [any, any, any, any],
ability: 'overgrow',
heldItem: null,
friendship: 70,
isShiny: false,
hatchedAt: Date.now(),
pokeball: 'pokeball',
}))
const party: (string | null)[] = [creatures[0]!.id, null, null, null, null, null]
if (creatureCount > 1) party[1] = creatures[1]!.id
if (creatureCount > 2) party[2] = creatures[2]!.id
return {
version: 2,
party,
boxes: [
{ name: 'Box 1', slots: Array(30).fill(null) as (string | null)[] },
{ name: 'Box 2', slots: Array(30).fill(null) as (string | null)[] },
],
creatures,
eggs: [],
dex: [],
bag: { items: [] },
stats: {
totalTurns: 10,
consecutiveDays: 5,
lastActiveDate: new Date().toISOString().split('T')[0],
totalEggsObtained: 0,
totalEvolutions: 0,
battlesWon: 3,
battlesLost: 1,
},
}
}
// ─── Default data ───
describe('getDefaultBuddyData', () => {
test('returns v2 data with correct structure', async () => {
@@ -30,3 +92,236 @@ describe('getDefaultBuddyData', () => {
expect(creature.pokeball).toBe('pokeball')
})
})
// ─── Party operations ───
describe('addToParty', () => {
test('adds creature to first empty slot', () => {
const data = makeData()
const result = addToParty(data, 'new-creature')
expect(result.added).toBe(true)
expect(result.data.party[1]).toBe('new-creature')
})
test('returns false when party is full', () => {
const data = makeData()
data.party = ['c1', 'c2', 'c3', 'c4', 'c5', 'c6']
const result = addToParty(data, 'new-creature')
expect(result.added).toBe(false)
})
})
describe('removeFromParty', () => {
test('removes creature at index', () => {
const data = makeData(2)
const updated = removeFromParty(data, 1)
expect(updated.party[1]).toBeNull()
})
test('does nothing for out-of-bounds index', () => {
const data = makeData()
const updated = removeFromParty(data, 10)
expect(updated.party).toEqual(data.party)
})
})
describe('swapPartySlots', () => {
test('swaps two party slots', () => {
const data = makeData(2)
const updated = swapPartySlots(data, 0, 1)
expect(updated.party[0]).toBe('creature-1')
expect(updated.party[1]).toBe('creature-0')
})
})
describe('setActivePartyMember', () => {
test('swaps creature to slot 0', () => {
const data = makeData(2)
const updated = setActivePartyMember(data, 'creature-1')
expect(updated.party[0]).toBe('creature-1')
expect(updated.party[1]).toBe('creature-0')
})
test('no change if already active', () => {
const data = makeData()
const updated = setActivePartyMember(data, 'creature-0')
expect(updated).toEqual(data)
})
})
// ─── PC Box operations ───
describe('depositToBox', () => {
test('deposits creature to first empty box slot', () => {
const data = makeData()
const result = depositToBox(data, 'box-creature')
expect(result.deposited).toBe(true)
expect(result.data.boxes[0]!.slots[0]).toBe('box-creature')
})
test('fills second box when first is full', () => {
const data = makeData()
data.boxes[0]!.slots = Array(30).fill('x')
const result = depositToBox(data, 'box-creature')
expect(result.deposited).toBe(true)
expect(result.data.boxes[1]!.slots[0]).toBe('box-creature')
})
})
describe('withdrawFromBox', () => {
test('withdraws creature from box', () => {
const data = makeData()
data.boxes[0]!.slots[5] = 'box-creature'
const result = withdrawFromBox(data, 'box-creature')
expect(result.withdrawn).toBe(true)
expect(result.data.boxes[0]!.slots[5]).toBeNull()
})
test('returns false when creature not in boxes', () => {
const data = makeData()
const result = withdrawFromBox(data, 'nonexistent')
expect(result.withdrawn).toBe(false)
})
})
describe('moveInBox', () => {
test('moves creature between slots', () => {
const data = makeData()
data.boxes[0]!.slots[0] = 'moving-creature'
const updated = moveInBox(data, 0, 0, 0, 5)
expect(updated.boxes[0]!.slots[0]).toBeNull()
expect(updated.boxes[0]!.slots[5]).toBe('moving-creature')
})
test('does nothing for empty source slot', () => {
const data = makeData()
const updated = moveInBox(data, 0, 0, 0, 5)
expect(updated).toEqual(data)
})
})
describe('renameBox', () => {
test('renames a box', () => {
const data = makeData()
const updated = renameBox(data, 0, 'My Box')
expect(updated.boxes[0]!.name).toBe('My Box')
})
})
describe('findCreatureLocation', () => {
test('finds creature in party', () => {
const data = makeData()
const loc = findCreatureLocation(data, 'creature-0')
expect(loc).toEqual({ area: 'party', slot: 0 })
})
test('finds creature in box', () => {
const data = makeData()
data.boxes[0]!.slots[3] = 'box-creature'
const loc = findCreatureLocation(data, 'box-creature')
expect(loc).toEqual({ area: 'box', slot: 3, boxIndex: 0 })
})
test('returns null for nonexistent', () => {
const data = makeData()
expect(findCreatureLocation(data, 'nonexistent')).toBeNull()
})
})
describe('releaseCreature', () => {
test('removes creature from party and creatures array', () => {
const data = makeData(2)
const updated = releaseCreature(data, 'creature-1')
expect(updated.creatures.find(c => c.id === 'creature-1')).toBeUndefined()
})
})
describe('getTotalCreatureCount', () => {
test('returns creature count', () => {
expect(getTotalCreatureCount(makeData(3))).toBe(3)
})
})
describe('getAllCreatureIds', () => {
test('returns all ids', () => {
expect(getAllCreatureIds(makeData(2))).toEqual(['creature-0', 'creature-1'])
})
})
// ─── Bag operations ───
describe('addItemToBag', () => {
test('adds new item', () => {
const data = makeData()
const updated = addItemToBag(data, 'potion', 3)
expect(updated.bag.items).toEqual([{ id: 'potion', count: 3 }])
})
test('stacks existing item', () => {
const data = makeData()
const withItem = addItemToBag(data, 'potion', 2)
const stacked = addItemToBag(withItem, 'potion', 3)
expect(stacked.bag.items[0]!.count).toBe(5)
})
})
describe('removeItemFromBag', () => {
test('removes item quantity', () => {
const data = makeData()
const withItem = addItemToBag(data, 'potion', 5)
const result = removeItemFromBag(withItem, 'potion', 3)
expect(result.removed).toBe(true)
expect(result.data.bag.items[0]!.count).toBe(2)
})
test('removes item entirely when count reaches 0', () => {
const data = makeData()
const withItem = addItemToBag(data, 'potion', 2)
const result = removeItemFromBag(withItem, 'potion', 2)
expect(result.removed).toBe(true)
expect(result.data.bag.items.length).toBe(0)
})
test('returns false when not enough items', () => {
const data = makeData()
const withItem = addItemToBag(data, 'potion', 1)
const result = removeItemFromBag(withItem, 'potion', 5)
expect(result.removed).toBe(false)
})
test('returns false for nonexistent item', () => {
const data = makeData()
const result = removeItemFromBag(data, 'potion', 1)
expect(result.removed).toBe(false)
})
})
describe('getItemCount', () => {
test('returns count for existing item', () => {
const data = makeData()
const withItem = addItemToBag(data, 'potion', 3)
expect(getItemCount(withItem, 'potion')).toBe(3)
})
test('returns 0 for nonexistent item', () => {
expect(getItemCount(makeData(), 'potion')).toBe(0)
})
})
// ─── Stats ───
describe('updateDailyStats', () => {
test('same day does not increment consecutive', () => {
const data = makeData()
const updated = updateDailyStats(data)
expect(updated.stats.consecutiveDays).toBe(data.stats.consecutiveDays)
})
})
describe('incrementTurns', () => {
test('increments totalTurns by 1', () => {
const data = makeData()
const updated = incrementTurns(data)
expect(updated.stats.totalTurns).toBe(data.stats.totalTurns + 1)
})
})