import { describe, expect, it } from 'vitest'; import type { CharacterChatRecord, CompanionRenderState, GameState, StoryMoment } from '../../types'; import { AnimationState, WorldType } from '../../types'; import { buildAdventureStatistics, buildCanvasCompanionRenderStates, buildCharacterChatSummaries, buildRpgRuntimeDialogueIndicator, } from '../rpg-runtime-shell/useRpgRuntimeShellViewModel'; function createBaseGameState(): GameState { return { worldType: WorldType.WUXIA, customWorldProfile: null, playerCharacter: null, runtimeStats: { playTimeMs: 0, lastPlayTickAt: null, hostileNpcsDefeated: 3, questsAccepted: 2, itemsUsed: 4, scenesTraveled: 5, }, currentScene: 'Story', storyHistory: [], characterChats: {}, animationState: AnimationState.IDLE, currentEncounter: null, npcInteractionActive: false, currentScenePreset: { id: 'scene-1', name: '断桥旧哨', description: '测试场景', imageSrc: '/scene.png', treasureHints: [], npcs: [], }, sceneHostileNpcs: [], playerX: 0, playerOffsetY: 0, playerFacing: 'right', playerActionMode: 'idle', scrollWorld: false, inBattle: false, playerHp: 100, playerMaxHp: 100, playerMana: 20, playerMaxMana: 20, playerSkillCooldowns: {}, activeBuildBuffs: [], activeCombatEffects: [], playerCurrency: 18, playerInventory: [ { id: 'item-1', name: '药草', description: '恢复道具', quantity: 2, category: 'consumable', rarity: 'common', tags: [], value: 1, }, { id: 'item-2', name: '布料', description: '材料', quantity: 3, category: 'material', rarity: 'common', tags: [], value: 1, }, ], playerEquipment: { weapon: null, armor: null, relic: null, }, npcStates: {}, quests: [ { id: 'quest-1', issuerNpcId: 'npc-1', issuerNpcName: '老周', sceneId: 'scene-1', title: '寻回包裹', description: '找回丢失的包裹', summary: '一项测试任务', objective: { kind: 'deliver_item', targetItemId: 'item-1', requiredCount: 1, }, progress: 1, status: 'completed', reward: { affinityBonus: 2, currency: 10, items: [], }, rewardText: '测试奖励', }, { id: 'quest-2', issuerNpcId: 'npc-2', issuerNpcName: '阿青', sceneId: 'scene-1', title: '护送商队', description: '保护商队通行', summary: '另一项测试任务', objective: { kind: 'reach_scene', targetSceneId: 'scene-2', requiredCount: 1, }, progress: 1, status: 'turned_in', reward: { affinityBonus: 3, currency: 20, items: [], }, rewardText: '测试奖励', }, ], roster: [ { npcId: 'npc-roster', characterId: 'char-roster', joinedAtAffinity: 10, hp: 90, maxHp: 90, mana: 12, maxMana: 12, skillCooldowns: {}, }, ], companions: [ { npcId: 'npc-active', characterId: 'char-active', joinedAtAffinity: 18, hp: 100, maxHp: 100, mana: 16, maxMana: 16, skillCooldowns: {}, }, { npcId: 'npc-encounter', characterId: 'char-encounter', joinedAtAffinity: 22, hp: 88, maxHp: 88, mana: 14, maxMana: 14, skillCooldowns: {}, }, ], currentBattleNpcId: null, currentNpcBattleMode: null, currentNpcBattleOutcome: null, sparReturnEncounter: null, sparPlayerHpBefore: null, sparPlayerMaxHpBefore: null, sparStoryHistoryBefore: null, }; } describe('useRpgRuntimeShellViewModel helpers', () => { it('builds a dialogue indicator only for active npc dialogue playback', () => { const state = { ...createBaseGameState(), currentEncounter: { id: 'npc-encounter', kind: 'npc' as const, npcName: '山道客', npcDescription: '拦路人', npcAvatar: '/npc.png', context: '山道相遇', }, }; const story = { text: '继续对话', displayMode: 'dialogue' as const, dialogue: [ { speaker: 'npc' as const, text: '先别急着出手。', }, { speaker: 'player' as const, text: '那你想说什么?', }, ], options: [], } satisfies StoryMoment; expect( buildRpgRuntimeDialogueIndicator({ isLoading: true, visibleGameState: state, visibleCurrentStory: story, }), ).toEqual({ showPlayer: true, showEncounter: true, activeSpeaker: 'player', }); expect( buildRpgRuntimeDialogueIndicator({ isLoading: false, visibleGameState: state, visibleCurrentStory: story, }), ).toBeNull(); }); it('derives compact chat summaries and hides the active encounter companion from canvas renders', () => { const chatSummaries = buildCharacterChatSummaries({ 'char-active': { history: [], summary: '已经建立起稳定默契。', updatedAt: null, }, 'char-roster': { history: [], summary: '仍在营地观望局势。', updatedAt: null, }, } satisfies Record); expect(chatSummaries).toEqual({ 'char-active': '已经建立起稳定默契。', 'char-roster': '仍在营地观望局势。', }); const visibleCompanionRenderStates = [ { npcId: 'npc-active' }, { npcId: 'npc-encounter' }, ] as CompanionRenderState[]; const visibleGameState = { ...createBaseGameState(), currentEncounter: { id: 'npc-encounter', kind: 'npc' as const, npcName: '山道客', npcDescription: '拦路人', npcAvatar: '/npc.png', context: '山道相遇', }, }; expect( buildCanvasCompanionRenderStates({ visibleCompanionRenderStates, visibleGameState, }), ).toEqual([{ npcId: 'npc-active' }]); }); it('aggregates adventure statistics from runtime and visible state slices', () => { const gameState = createBaseGameState(); const statistics = buildAdventureStatistics({ gameState, visibleGameState: gameState, livePlayTimeMs: 3210, }); expect(statistics).toEqual({ playTimeMs: 3210, hostileNpcsDefeated: 3, questsAccepted: 2, questsCompleted: 2, questsTurnedIn: 1, itemsUsed: 4, scenesTraveled: 5, currentSceneName: '断桥旧哨', playerCurrency: 18, playerLevel: 1, playerCurrentLevelXp: 0, playerXpToNextLevel: 60, playerTotalXp: 0, inventoryItemCount: 5, inventoryStackCount: 2, activeCompanionCount: 2, rosterCompanionCount: 1, }); }); });