1
This commit is contained in:
171
src/components/AdventureEntityModal.test.tsx
Normal file
171
src/components/AdventureEntityModal.test.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { afterEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
AnimationState,
|
||||
type Encounter,
|
||||
type GameState,
|
||||
type EquipmentLoadout,
|
||||
WorldType,
|
||||
} from '../types';
|
||||
import { AdventureEntityModal } from './AdventureEntityModal';
|
||||
|
||||
vi.mock('./CharacterAnimator', () => ({
|
||||
CharacterAnimator: () => <div data-testid="character-portrait" />,
|
||||
}));
|
||||
|
||||
vi.mock('./MedievalNpcAnimator', () => ({
|
||||
MedievalNpcAnimator: () => <div data-testid="medieval-npc-portrait" />,
|
||||
}));
|
||||
|
||||
vi.mock('./HostileNpcAnimator', () => ({
|
||||
HostileNpcAnimator: () => <div data-testid="hostile-npc-portrait" />,
|
||||
}));
|
||||
|
||||
function createGameState(overrides: Partial<GameState> = {}): GameState {
|
||||
return {
|
||||
worldType: WorldType.WUXIA,
|
||||
customWorldProfile: null,
|
||||
playerCharacter: null,
|
||||
runtimeStats: {
|
||||
playTimeMs: 0,
|
||||
lastPlayTickAt: null,
|
||||
hostileNpcsDefeated: 0,
|
||||
questsAccepted: 0,
|
||||
itemsUsed: 0,
|
||||
scenesTraveled: 0,
|
||||
},
|
||||
currentScene: 'test-scene',
|
||||
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: 30,
|
||||
playerMaxMana: 30,
|
||||
playerSkillCooldowns: {},
|
||||
activeCombatEffects: [],
|
||||
playerCurrency: 0,
|
||||
playerInventory: [],
|
||||
playerEquipment: {} as EquipmentLoadout,
|
||||
npcStates: {},
|
||||
quests: [],
|
||||
roster: [],
|
||||
companions: [],
|
||||
currentBattleNpcId: null,
|
||||
currentNpcBattleMode: null,
|
||||
currentNpcBattleOutcome: null,
|
||||
sparReturnEncounter: null,
|
||||
sparPlayerHpBefore: null,
|
||||
sparPlayerMaxHpBefore: null,
|
||||
sparStoryHistoryBefore: null,
|
||||
...overrides,
|
||||
} as GameState;
|
||||
}
|
||||
|
||||
function createEncounter(overrides: Partial<Encounter> = {}): Encounter {
|
||||
return {
|
||||
id: 'runtime-npc',
|
||||
kind: 'npc',
|
||||
npcName: '雾中来客',
|
||||
npcDescription: '带着临时生成形象的相遇者',
|
||||
npcAvatar: '/avatar.png',
|
||||
context: '桥边试探',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('NPC 详情立绘优先展示遭遇实例形象,而不是 characterId 对应预设', () => {
|
||||
const encounter = createEncounter({
|
||||
characterId: 'sword-princess',
|
||||
imageSrc: '/runtime-npc-preview.png',
|
||||
});
|
||||
|
||||
render(
|
||||
<AdventureEntityModal
|
||||
selection={{ kind: 'npc', encounter }}
|
||||
gameState={createGameState()}
|
||||
onClose={() => undefined}
|
||||
/>,
|
||||
);
|
||||
|
||||
const portrait = screen.getByAltText('雾中来客');
|
||||
|
||||
expect(portrait.getAttribute('src')).toBe('/runtime-npc-preview.png');
|
||||
expect(screen.queryByTestId('character-portrait')).toBeNull();
|
||||
});
|
||||
|
||||
test('NPC 背包物品空 id 会被规范成稳定渲染 id', () => {
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => undefined);
|
||||
const encounter = createEncounter();
|
||||
|
||||
render(
|
||||
<AdventureEntityModal
|
||||
selection={{ kind: 'npc', encounter }}
|
||||
gameState={createGameState({
|
||||
npcStates: {
|
||||
'runtime-npc': {
|
||||
affinity: 0,
|
||||
relationState: { affinity: 0, stance: 'neutral' },
|
||||
helpUsed: false,
|
||||
chattedCount: 0,
|
||||
giftsGiven: 0,
|
||||
inventory: [
|
||||
{
|
||||
id: '',
|
||||
category: '材料',
|
||||
name: '裂纹石片',
|
||||
quantity: 1,
|
||||
rarity: 'common',
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
category: '材料',
|
||||
name: '裂纹石片',
|
||||
quantity: 2,
|
||||
rarity: 'common',
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
recruited: false,
|
||||
revealedFacts: [],
|
||||
knownAttributeRumors: [],
|
||||
firstMeaningfulContactResolved: false,
|
||||
seenBackstoryChapterIds: [],
|
||||
},
|
||||
},
|
||||
})}
|
||||
onClose={() => undefined}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getAllByTitle(/裂纹石片 x/)).toHaveLength(2);
|
||||
expect(
|
||||
consoleErrorSpy.mock.calls.some((call) =>
|
||||
call.some(
|
||||
(arg) =>
|
||||
typeof arg === 'string' &&
|
||||
arg.includes('Encountered two children with the same key'),
|
||||
),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
Reference in New Issue
Block a user