/* @vitest-environment jsdom */ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { afterEach, expect, test, vi } from 'vitest'; import { AnimationState, type Character } from '../types'; import { CharacterIdentityBadges, CharacterSkillsList, PlayerLevelProgress, } from './CharacterInfoShared'; function createSkill( name: string, style: Character['skills'][number]['style'], ): Character['skills'][number] { return { id: '', name, animation: AnimationState.IDLE, damage: 12, manaCost: 4, cooldownTurns: 2, range: 1, style, }; } afterEach(() => { vi.restoreAllMocks(); }); test('CharacterSkillsList falls back to stable render ids when skill ids are empty', async () => { const user = userEvent.setup(); const handleSelectSkill = vi.fn(); const consoleErrorSpy = vi .spyOn(console, 'error') .mockImplementation(() => undefined); render( , ); const buttons = screen.getAllByRole('button'); await user.click(buttons[0]!); await user.click(buttons[1]!); expect(handleSelectSkill).toHaveBeenNthCalledWith(1, 'skill-潮刃突进-0'); expect(handleSelectSkill).toHaveBeenNthCalledWith(2, 'skill-雾行转位-1'); 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); }); test('CharacterIdentityBadges renders role and level chips together', () => { render( , ); expect(screen.getByText('队长')).toBeTruthy(); expect(screen.getByText('Lv.7')).toBeTruthy(); }); test('PlayerLevelProgress renders xp progress details', () => { render( , ); expect(screen.getByText('Lv.6')).toBeTruthy(); expect(screen.getByText('72/120')).toBeTruthy(); });