import { describe, expect, it } from 'vitest' import { World } from '../src/renderer/game/engine/runtime/World' import { calcGiftGain, GIFT_TIERS, giftNpc } from '../src/renderer/game/engine/runtime/Systems/diplomacy' 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('0.1.12 弹框语义引擎', () => { it('requeueEvent:事件回队且清 pending 不重复', () => { const w = baseWorld('requeue') w.state.pendingEvent = 'ev-youdao' w.requeueEvent('ev-youdao') expect(w.state.pendingEvent).toBeUndefined() expect(w.state.eventQueue).toContain('ev-youdao') // 再次 requeue 不重复 w.requeueEvent('ev-youdao') expect(w.state.eventQueue.filter((e) => e === 'ev-youdao').length).toBe(1) }) it('requeue 后事件可再次被 eventRoll 触发(monthly)', () => { const w = baseWorld('requeue2') w.state.pendingEvent = 'ev-youdao' w.requeueEvent('ev-youdao') // 下一月 eventRoll 会从队列抽出 w.advanceMonth() expect(w.state.eventQueue).not.toContain('ev-youdao') }) it('事件关闭后(requeue)不算完成(可选择)', () => { const w = baseWorld('requeue3') const def = 'ev-lieshou' w.state.pendingEvent = def w.requeueEvent(def) expect(w.state.completedEvents).not.toContain(def) }) }) describe('赠礼档位', () => { it.each(GIFT_TIERS.map((t) => [t] as const))('档位 %i 换算关系为 +N', (t) => { const gain = calcGiftGain(t) expect(gain).toBe(Math.max(1, Math.round(t / 12))) expect(gain).toBeGreaterThan(0) }) it.each(GIFT_TIERS.map((t) => [t] as const))('档位 %i 赠予后关系精确', (t) => { const w = baseWorld(`gift-${t}`) const npc = w.state.npcFamilies['n-xuanying'] const before = npc.relation w.state.family.stones = 1000 giftNpc(w, 'n-xuanying', t) expect(npc.relation).toBe(Math.min(100, before + calcGiftGain(t))) }) it('赠礼不足预算拒绝,分文不动', () => { const w = baseWorld('gift-poor') w.state.family.stones = 10 const before = w.state.npcFamilies['n-xuanying'].relation const ok = giftNpc(w, 'n-xuanying', 40) expect(ok).toBe(false) expect(w.state.npcFamilies['n-xuanying'].relation).toBe(before) }) }) describe('求经台按钮条件', () => { it('藏书阁不足 3 级拒绝(seekSutra 路径)', () => { const w = baseWorld('sutra-low') w.state.family.buildings = { cangshu: 2 } expect(w.seekSutra()).toBe(false) }) it('藏书阁 3 级 + 灵石足够 + 冷却期外成功', () => { const w = baseWorld('sutra-ok') w.state.family.buildings = { cangshu: 3 } w.state.family.stones = 1000 expect(w.seekSutra()).toBe(true) expect(w.state.family.stones).toBe(850) }) it('两年冷却内再访拒绝', () => { const w = baseWorld('sutra-cd') w.state.family.buildings = { cangshu: 3 } w.state.family.stones = 1000 w.seekSutra() expect(w.seekSutra()).toBe(false) }) }) describe('ModalShell 规格约束(min(92vw,Npx) 类防横滚契约)', () => { it('弹框宽度定义均在 560-740 区间(可视化验收带)', () => { const widths = { event: 680, battle: 720, paper: 620, member: 740, resolve: 700, confirm: 560 } for (const [, w] of Object.entries(widths)) { expect(w).toBeGreaterThanOrEqual(540) expect(w).toBeLessThanOrEqual(760) } }) })