/* @vitest-environment jsdom */ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { afterEach, expect, test, vi } from 'vitest'; import { buildCustomWorldPlayableCharacters, } from '../../data/characterPresets'; import { type Character, type CustomWorldProfile, WorldType, } from '../../types'; import { RpgEntryCharacterSelectView } from './RpgEntryCharacterSelectView'; vi.mock('../../data/characterPresets', () => ({ ROLE_TEMPLATE_CHARACTERS: [], buildCustomWorldPlayableCharacters: vi.fn(), })); vi.mock('../CharacterAnimator', () => ({ CharacterAnimator: ({ character }: { character: Character }) => (
{character.name}
), })); vi.mock('../CharacterDetailModal', () => ({ CharacterDetailModal: () => null, })); vi.mock('../SelectionCustomizationModals', () => ({ CharacterDraftModal: () => null, })); function createCharacter(name: string, title: string): Character { return { id: '', name, title, description: `${name}的定位描述`, backstory: `${name}的背景故事`, personality: `${name} 冷静 果断`, gender: 'female', portrait: `/portraits/${name}.png`, attributes: { strength: 10, agility: 11, intelligence: 12, spirit: 13, }, skills: [], } as unknown as Character; } afterEach(() => { vi.restoreAllMocks(); }); test('custom world character selection stays stable when character ids are empty', async () => { const user = userEvent.setup(); const handleConfirm = vi.fn(); const consoleErrorSpy = vi .spyOn(console, 'error') .mockImplementation(() => undefined); vi.mocked(buildCustomWorldPlayableCharacters).mockReturnValue([ createCharacter('沈砺', '潮锋斥候'), createCharacter('闻潮', '雾海哨兵'), ]); HTMLElement.prototype.scrollTo = function scrollTo( this: HTMLElement, options?: ScrollToOptions | number, ) { if (typeof options === 'object' && options) { if (typeof options.left === 'number') { this.scrollLeft = options.left; } if (typeof options.top === 'number') { this.scrollTop = options.top; } } this.dispatchEvent(new Event('scroll')); }; vi .spyOn(HTMLElement.prototype, 'getBoundingClientRect') .mockImplementation(function mockGetBoundingClientRect(this: HTMLElement) { if ((this as HTMLElement).dataset.carouselCard === 'true') { return { width: 240, height: 360, top: 0, right: 240, bottom: 360, left: 0, x: 0, y: 0, toJSON: () => ({}), } as DOMRect; } return { width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0, x: 0, y: 0, toJSON: () => ({}), } as DOMRect; }); render( {}} onConfirm={handleConfirm} />, ); expect(screen.getByText(/潮骨:/u)).toBeTruthy(); expect(screen.queryByText(/力量:/u)).toBeNull(); await user.click(screen.getByRole('button', { name: /闻潮/u })); await waitFor(() => { expect(screen.getByRole('button', { name: /查看闻潮的详情/u })).toBeTruthy(); }); await user.click(screen.getByRole('button', { name: /进入营地/u })); expect(handleConfirm).toHaveBeenCalledWith( expect.objectContaining({ name: '闻潮', title: '雾海哨兵', }), ); const duplicateKeyCalls = consoleErrorSpy.mock.calls.filter((call) => call.some( (arg) => typeof arg === 'string' && arg.includes('Encountered two children with the same key'), ), ); expect(duplicateKeyCalls).toHaveLength(0); });