294 lines
7.2 KiB
TypeScript
294 lines
7.2 KiB
TypeScript
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<string, CharacterChatRecord>);
|
|
|
|
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,
|
|
});
|
|
});
|
|
});
|