Files
Genarrative/src/components/rpg-runtime-shell/RpgRuntimeShell.test.tsx

325 lines
8.3 KiB
TypeScript

/* @vitest-environment jsdom */
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, expect, test, vi } from 'vitest';
import {
AnimationState,
WorldType,
type GameState,
type StoryMoment,
} from '../../types';
import { RpgRuntimeShell } from './RpgRuntimeShell';
import type { RpgRuntimeShellProps } from './types';
const noop = () => {};
const asyncFalse = async () => false;
vi.mock('../auth/AuthUiContext', () => ({
useAuthUi: () => null,
}));
vi.mock('./useRpgRuntimeShellViewModel', () => ({
useRpgRuntimeShellViewModel: () => ({
selectionStage: 'platform',
setSelectionStage: () => {},
overlayPanel: null,
openOverlayPanel: () => {},
closeOverlayPanel: () => {},
selectedSceneEntity: null,
setSelectedSceneEntity: () => {},
openPartyMemberDetails: () => {},
closeAdventureEntityModal: () => {},
showTeamModal: false,
openCampModal: () => {},
closeCampModal: () => {},
resetForSaveAndExit: () => {},
shouldMountAdventureEntityModal: false,
shouldMountCampModal: false,
shouldMountMapModal: false,
shouldMountCharacterChatModal: false,
shouldMountNpcModals: false,
visibleGameState: mockVisibleGameState,
visibleCurrentStory: {
text: '测试故事',
options: [],
},
sceneTransitionPhase: 'idle',
sceneTransitionToken: 0,
setSceneTransitionDurations: () => {},
isCharacterSelectionStage: false,
shouldHideStoryOptions: false,
hideSelectionHero: false,
dialogueIndicator: {
showPlayer: false,
showEncounter: false,
activeSpeaker: null,
},
characterChatSummaries: {},
canvasCompanionRenderStates: [],
adventureStatistics: {
playTimeMs: 0,
hostileNpcsDefeated: 0,
questsAccepted: 0,
questsCompleted: 0,
questsTurnedIn: 0,
itemsUsed: 0,
scenesTraveled: 0,
currentSceneName: '测试场景',
playerCurrency: 0,
inventoryItemCount: 0,
inventoryStackCount: 0,
activeCompanionCount: 0,
rosterCompanionCount: 0,
},
handleSceneTransitionChoice: () => {},
}),
}));
vi.mock('./RpgRuntimeCanvasStage', () => ({
RpgRuntimeCanvasStage: () => <div></div>,
}));
vi.mock('./RpgRuntimeOverlayHost', () => ({
RpgRuntimeOverlayHost: () => null,
}));
vi.mock('./RpgRuntimeStageRouter', () => ({
RpgRuntimeStageRouter: () => <div></div>,
}));
let mockVisibleGameState: GameState;
function createGameState(
runtimeMode: GameState['runtimeMode'],
runtimePersistenceDisabled?: boolean,
): GameState {
return {
worldType: WorldType.CUSTOM,
customWorldProfile: null,
playerCharacter: {
id: 'player-1',
name: '测试角色',
title: '测试者',
description: '测试角色',
backstory: '测试背景',
personality: '冷静',
avatar: '',
portrait: '',
assetFolder: '',
assetVariant: '',
attributes: {
strength: 5,
agility: 5,
intelligence: 5,
spirit: 5,
},
backstoryReveal: {
publicSummary: '测试',
privateChatUnlockAffinity: 60,
chapters: [],
},
skills: [],
adventureOpenings: {},
},
runtimeMode,
runtimePersistenceDisabled: runtimePersistenceDisabled ?? false,
runtimeStats: {
playTimeMs: 0,
lastPlayTickAt: null,
hostileNpcsDefeated: 0,
questsAccepted: 0,
itemsUsed: 0,
scenesTraveled: 0,
},
playerProgression: {
level: 1,
currentLevelXp: 0,
totalXp: 0,
xpToNextLevel: 100,
},
currentScene: 'Story',
storyHistory: [],
characterChats: {},
animationState: AnimationState.IDLE,
currentEncounter: null,
npcInteractionActive: false,
currentScenePreset: null,
sceneHostileNpcs: [],
playerX: 0,
playerOffsetY: 0,
playerFacing: 'right',
playerActionMode: 'idle',
scrollWorld: false,
inBattle: false,
playerHp: 100,
playerMaxHp: 100,
playerMana: 50,
playerMaxMana: 50,
playerSkillCooldowns: {},
activeCombatEffects: [],
playerCurrency: 0,
playerInventory: [],
playerEquipment: {
weapon: null,
armor: null,
relic: null,
},
npcStates: {},
quests: [],
roster: [],
companions: [],
currentBattleNpcId: null,
currentNpcBattleMode: null,
currentNpcBattleOutcome: null,
sparReturnEncounter: null,
sparPlayerHpBefore: null,
sparPlayerMaxHpBefore: null,
sparStoryHistoryBefore: null,
};
}
function buildProps(
runtimeMode: GameState['runtimeMode'],
runtimePersistenceDisabled?: boolean,
): RpgRuntimeShellProps {
const gameState = createGameState(runtimeMode, runtimePersistenceDisabled);
const currentStory: StoryMoment = {
text: '测试故事',
options: [],
};
mockVisibleGameState = gameState;
return {
session: {
gameState,
currentStory,
isLoading: false,
aiError: null,
bottomTab: 'adventure',
setBottomTab: () => {},
isMapOpen: false,
setIsMapOpen: () => {},
},
story: {
displayedOptions: [],
canRefreshOptions: false,
handleRefreshOptions: () => {},
handleChoice: () => {},
handleNpcChatInput: () => false,
refreshNpcChatOptions: () => false,
exitNpcChat: () => false,
handleMapTravelToScene: () => false,
npcUi: {
tradeModal: null,
giftModal: null,
recruitModal: null,
setTradeMode: noop,
selectTradeNpcItem: noop,
selectTradePlayerItem: noop,
setTradeQuantity: noop,
closeTradeModal: noop,
confirmTrade: noop,
selectGiftItem: noop,
closeGiftModal: noop,
confirmGift: noop,
selectRecruitRelease: noop,
closeRecruitModal: noop,
confirmRecruit: noop,
},
characterChatUi: {
modal: null,
openChat: noop,
closeChat: noop,
setDraft: noop,
useSuggestion: noop,
refreshSuggestions: noop,
sendDraft: noop,
},
inventoryUi: {
useInventoryItem: asyncFalse,
equipInventoryItem: asyncFalse,
unequipItem: asyncFalse,
playerCurrency: 0,
currencyText: '0',
backpackItems: [],
equipmentSlots: [],
forgeRecipes: [],
craftRecipe: asyncFalse,
dismantleItem: asyncFalse,
reforgeItem: asyncFalse,
},
battleRewardUi: {
reward: null,
dismiss: noop,
},
questUi: {
acknowledgeQuestCompletion: noop,
claimQuestReward: () => null,
},
npcChatQuestOfferUi: {
replacePendingOffer: () => false,
abandonPendingOffer: () => false,
acceptPendingOffer: () => null,
},
goalUi: {
goalStack: {
northStarGoal: null,
activeGoal: null,
immediateStepGoal: null,
supportGoals: [],
},
pulse: null,
dismissPulse: noop,
},
},
entry: {
hasSavedGame: false,
savedSnapshot: null,
handleContinueGame: () => {},
handleStartNewGame: () => {},
handleSaveAndExit: () => {},
handleCustomWorldSelect: () => {},
handleBackToWorldSelect: () => {},
handleCharacterSelect: () => {},
},
companions: {
companionRenderStates: [],
buildCompanionRenderStates: () => [],
onBenchCompanion: () => {},
onActivateRosterCompanion: () => {},
},
audio: {
musicVolume: 0.5,
onMusicVolumeChange: () => {},
},
};
}
beforeEach(() => {
mockVisibleGameState = createGameState('play');
});
test('结果页测试入口可显示结束测试按钮并触发退出回调', async () => {
const user = userEvent.setup();
const onExitRuntimePreview = vi.fn();
render(
<RpgRuntimeShell
{...buildProps('play', true)}
onExitRuntimePreview={onExitRuntimePreview}
showRuntimePreviewExit
/>,
);
await user.click(screen.getByRole('button', { name: '结束测试' }));
expect(onExitRuntimePreview).toHaveBeenCalledTimes(1);
});
test('正式运行态不显示结束测试按钮', () => {
render(<RpgRuntimeShell {...buildProps('play')} />);
expect(screen.queryByRole('button', { name: '结束测试' })).toBeNull();
});