/* @vitest-environment jsdom */ import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import type { GenerateDialogState, SpecFormValues, UploadTarget, } from './ImageCanvasEditorTypes'; import { ImageCanvasSpecGenerationPanelView } from './ImageCanvasSpecGenerationPanelView'; function createSpecDialog( patch: Partial = {}, ): GenerateDialogState { return { mode: 'spec', prompt: '', status: 'idle', specType: 'character', specValues: { playSetting: 'RPG玩法', artStyle: '像素风', bodyRatio: '3', characterView: '右向斜侧身站姿', customPrompt: '', }, ...patch, }; } function renderPanel({ dialog, onUpdateSpecFormValue = vi.fn(), onRequestUpload = vi.fn(), onSubmit = vi.fn(), }: { dialog: GenerateDialogState; onUpdateSpecFormValue?: (key: keyof SpecFormValues, value: string) => void; onRequestUpload?: (target: UploadTarget) => void; onSubmit?: (dialog: GenerateDialogState) => void; }) { render( , ); } describe('ImageCanvasSpecGenerationPanelView', () => { it('renders character spec fields and forwards updates', () => { const updateSpecFormValue = vi.fn(); renderPanel({ dialog: createSpecDialog(), onUpdateSpecFormValue: updateSpecFormValue, }); fireEvent.change(screen.getByLabelText('玩法设定'), { target: { value: '战棋玩法' }, }); fireEvent.change(screen.getByLabelText('美术风格'), { target: { value: '水彩' }, }); fireEvent.change(screen.getByLabelText('头身比'), { target: { value: '5' }, }); fireEvent.change(screen.getByLabelText('角色视角'), { target: { value: '左向三分之二侧身站姿' }, }); expect(updateSpecFormValue).toHaveBeenCalledWith('playSetting', '战棋玩法'); expect(updateSpecFormValue).toHaveBeenCalledWith('artStyle', '水彩'); expect(updateSpecFormValue).toHaveBeenCalledWith('bodyRatio', '5'); expect(updateSpecFormValue).toHaveBeenCalledWith( 'characterView', '左向三分之二侧身站姿', ); }); it('renders custom prompt and hides reference upload for icon specs', () => { const updateSpecFormValue = vi.fn(); renderPanel({ dialog: createSpecDialog({ specType: 'custom', specValues: { playSetting: '', artStyle: '', bodyRatio: '3', characterView: '', customPrompt: '自定义提示', }, }), onUpdateSpecFormValue: updateSpecFormValue, }); fireEvent.change(screen.getByLabelText('自定义规范提示词'), { target: { value: '新的规范提示' }, }); expect(updateSpecFormValue).toHaveBeenCalledWith( 'customPrompt', '新的规范提示', ); cleanup(); renderPanel({ dialog: createSpecDialog({ specType: 'icon' }) }); expect(screen.queryByText('添加参考图')).toBeNull(); }); it('requests reference upload and submits while idle', () => { const requestUpload = vi.fn(); const submitSpec = vi.fn(); const dialog = createSpecDialog(); renderPanel({ dialog, onRequestUpload: requestUpload, onSubmit: submitSpec, }); fireEvent.click(screen.getByRole('button', { name: '添加参考图' })); fireEvent.click(screen.getByRole('button', { name: '提交生成规范' })); expect(requestUpload).toHaveBeenCalledWith('spec-reference'); expect(submitSpec).toHaveBeenCalledWith(dialog); }); it('disables controls while generating and renders failure state', () => { const submitSpec = vi.fn(); const { rerender } = render( , ); fireEvent.click(screen.getByRole('button', { name: '提交生成规范' })); expect((screen.getByLabelText('玩法设定') as HTMLInputElement).disabled).toBe( true, ); expect(submitSpec).not.toHaveBeenCalled(); rerender( , ); expect(screen.getByRole('alert').textContent).toContain('生成失败'); }); });