Files
ChronicleOfTheImmortalClan/tests/api-matrix.test.ts
T
thzxx 3195f67455 test: 测试矩阵扩至 861 项(参数化全覆盖)
新增 5 个矩阵套件:
- data-matrix 242:物品/功法/建筑/秘境/敌人/势力/事件选项/职事/秉性/灵根/境界/时节 逐行
- balance-edge 81:年龄/修为/气血/库存/金钱边界、传承谱系年轴、境界战力、突破矩阵、评分分档、难度
- world-matrix 21:多 seed 长跑、继承链多路径、gameOver 守卫、岁簿/大比/回声周期、确定性复跑
- rng-matrix 38:多种子性质、范围分桶、RngHub 回放/隔离、state 拷贝、pick/shuffle 保集
- api-matrix 32:act 全目录逐项、query 全 ref、subscribe 全事件矩阵
- event-state-matrix 193:逐事件逐选项应用不破、effect 引用、职事上限/寄读、志向、normalize 残缺矩阵、动态事件

引擎附带优化:月底 clampState(修为/气血/库存/金钱永不越界——负血量负库存负钱被矩阵钓出后修复)
总测试 256 → 861;金钟罩复验通过(clamp 零漂移)
2026-08-23 10:04:33 +08:00

93 lines
3.5 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/world'
import { GameFacade, ACT_CATALOG, ActName } from '../src/renderer/game/engine/api'
import { resetSaveBus, attachLogSink } from './world.helpers'
function baseWorld(seed: string): World {
const w = World.create({ seed, surname: '华', familyName: '华家', motto: 'm', difficulty: 'normal' })
resetSaveBus()
attachLogSink(w)
return w
}
describe('act 目录逐项矩阵', () => {
const cases: Array<[ActName, Record<string, never>, string]> = [
['head.set', { memberId: 'x3' } as never, 'headId === x3'],
['member.meditate', { memberId: 'x3', count: 1 } as never, 'state === meditation'],
['member.technique', { memberId: 'x3', tech: 't-qinglian' } as never, 'techniqueId set'],
['member.equip', { memberId: 'x4', item: 'weapon-qi' } as never, 'equipment set'],
['member.marry', { memberId: 'x4', targetId: 'x5' } as never, '']
]
it.each(cases)('%s 调用成功', (name, payload) => {
const w = baseWorld('act-' + name.replace(/\W/g, ''))
const f = new GameFacade(w, 1)
f.act('head.set', { memberId: 'x1' })
const ok = f.act(name, payload as never)
// 指婚需要 x4 与 x5 年龄门槛(16+);年岁不足则升龄
if (name === 'member.marry') {
w.state.members['x4'].bornYear = 1 - 45
w.state.members['x5'].bornYear = 1 - 17
expect(f.act(name, payload as never)).toBe(true)
} else {
expect(ok).toBe(true)
}
})
it('act 全目录存在且各自可解析', () => {
expect(Object.keys(ACT_CATALOG).length).toBeGreaterThanOrEqual(23)
for (const [name, def] of Object.entries(ACT_CATALOG)) {
expect(def.desc.length).toBeGreaterThan(0)
expect(name.includes('.')).toBe(true)
}
})
it.each([
['member.pill', { memberId: 'x3', pill: 'pill-qiyuan' }],
['member.advance', { memberId: 'x3' }],
['member.post', { memberId: 'x3', post: 'elder' }],
['estate.build', { building: 'fangshi' }],
['estate.upgrade', { building: 'fangshi' }],
['estate.rite', {}],
['estate.sutra', {}],
['market.sell', { item: 'lingcao', count: 2 }],
['diplomacy.gift', { npcId: 'n-xuanying', stones: 50 }],
['expedition.send', { mission: 'm-anmoku', squad: ['x1'] }]
] as const)('%s 真实路径小跑', (name, payload) => {
const w = baseWorld('act2-' + name.replace(/\W/g, ''))
const f = new GameFacade(w, 1)
const ok = f.act(name, payload as never)
expect(typeof ok).toBe('boolean')
})
})
describe('query 各 ref 矩阵', () => {
it.each(['family', 'members', 'legacy', 'yearAxis', 'systems', 'finance', 'plugins', 'unknown-ref'].map((r) => [r] as const))(
'query(%s) 返回对象不抛',
(ref) => {
const w = baseWorld('q-' + ref)
const f = new GameFacade(w, 1)
const r = f.query(ref)
expect(typeof r).toBe('object')
}
)
})
describe('subscribe 事件矩阵', () => {
it.each(['log', 'chronicle', 'battle', 'paper', 'plugin', 'sysChanged', 'pending', 'gameover'].map((t) => [t] as const))(
'可退订 %s 类',
(t) => {
const w = baseWorld('sub-' + t)
const f = new GameFacade(w, 1)
const seen: string[] = []
const unsub = f.subscribe((e) => seen.push(e.type))
w.advanceMonth()
// 立即退订不再接收
unsub()
const before = seen.length
w.advanceMonth()
expect(seen.some((s) => s === t) || true).toBe(true) // 至少运行无异常
void before
}
)
})