/* @vitest-environment jsdom */ import { fireEvent, render, screen, within } from '@testing-library/react'; import { createRef, useState } from 'react'; import { describe, expect, it, vi } from 'vitest'; import { setPlainTextEditorValue } from '../common/AutoGrowTextArea.test-utils'; import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView'; import type { GenerateDialogState, UploadTarget, } from './ImageCanvasEditorTypes'; function createDialog( patch: Partial = {}, ): GenerateDialogState { return { mode: 'generate', prompt: '初始提示', status: 'idle', generationReferences: [], imageModel: 'gemini-3.1-flash-image-preview', aspectRatio: '16:9', imageSize: '1K', ...patch, }; } function BasicGenerationHarness({ initialDialog = createDialog(), onRequestUpload = vi.fn(), onSubmit = vi.fn(), }: { initialDialog?: GenerateDialogState; onRequestUpload?: (target: UploadTarget) => void; onSubmit?: (dialog: GenerateDialogState) => void; }) { const [dialog, setDialog] = useState( initialDialog, ); const [isReferenceMenuOpen, setIsReferenceMenuOpen] = useState(false); const [isPickingReference, setIsPickingReference] = useState(false); const generationReferenceButtonRef = createRef(); return dialog ? (
node} buildPortalMenuStyle={() => ({ position: 'fixed', left: 0, top: 0 })} onRequestUpload={onRequestUpload} onToggleReferenceMenu={() => setIsReferenceMenuOpen((open) => !open)} onSubmit={onSubmit} /> {dialog.prompt} {dialog.aspectRatio} {dialog.imageSize} {dialog.imageModel} {dialog.status} {dialog.errorMessage ?? '-'} {String(isPickingReference)}
) : null; } describe('ImageCanvasBasicGenerationComposerView', () => { it('updates prompt, clears failed state, uploads references and submits', () => { const requestUpload = vi.fn(); const submitGeneration = vi.fn(); render( , ); setPlainTextEditorValue(screen.getByLabelText('生成提示词'), '新的提示'); const panel = screen.getByRole('dialog', { name: '生成图片' }); expect( within(panel).queryByRole('textbox', { name: '资源名称' }), ).toBeNull(); fireEvent.click(within(panel).getByRole('button', { name: '添加参考图' })); fireEvent.click(screen.getByRole('menuitem', { name: '上传图片' })); fireEvent.click(screen.getByRole('button', { name: '生成' })); expect(screen.getByLabelText('当前提示词').textContent).toBe('新的提示'); expect(screen.getByLabelText('当前状态').textContent).toBe('idle'); expect(screen.getByLabelText('当前错误').textContent).toBe('-'); expect(requestUpload).toHaveBeenCalledWith('generation-reference'); expect(submitGeneration).toHaveBeenCalledWith( expect.objectContaining({ prompt: '新的提示', status: 'idle', }), ); }); it('keeps references on the first row and appends a new add-reference entry', () => { render( , ); const panel = screen.getByRole('dialog', { name: '生成图片' }); const strip = panel.querySelector('.image-canvas-editor__reference-strip'); expect(strip).toBeTruthy(); expect(within(strip as HTMLElement).getByText('参考图A')).toBeTruthy(); expect(within(strip as HTMLElement).getByText('参考图B')).toBeTruthy(); expect( within(strip as HTMLElement).getByRole('button', { name: '添加参考图' }), ).toBeTruthy(); }); it('removes an existing reference from its hover delete button', () => { render( , ); fireEvent.click(screen.getByRole('button', { name: '删除参考图A' })); expect(screen.queryByText('参考图A')).toBeNull(); }); it('removes only the selected reference when multiple references are present', () => { const submitGeneration = vi.fn(); render( , ); fireEvent.click(screen.getByRole('button', { name: '删除参考图B' })); expect(screen.queryByText('参考图B')).toBeNull(); expect(screen.getByText('参考图A')).toBeTruthy(); expect(screen.getByText('参考图C')).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: '生成' })); expect(submitGeneration).toHaveBeenCalledWith( expect.objectContaining({ generationReferences: [ expect.objectContaining({ id: 'ref-a' }), expect.objectContaining({ id: 'ref-c' }), ], }), ); }); it('shows only current image options and opens upward option panels', () => { render(); const panel = screen.getByRole('dialog', { name: '生成图片' }); fireEvent.click( within(panel).getByRole('button', { name: '生成图片尺寸 16:9·1K', }), ); const dimensionPanel = screen.getByRole('menu', { name: '生成图片尺寸选项', }); expect(within(dimensionPanel).getByText('比例')).toBeTruthy(); fireEvent.click(within(dimensionPanel).getByRole('button', { name: '2K' })); expect(screen.getByLabelText('当前尺寸').textContent).toBe('2K'); fireEvent.click( within(panel).getByRole('button', { name: '生成图片模型 nanobanana2', }), ); const modelPanel = screen.getByRole('menu', { name: '生成图片模型选项' }); fireEvent.click( within(modelPanel).getByRole('button', { name: /gpt-image-2/ }), ); expect(screen.getByLabelText('当前模型').textContent).toBe('gpt-image-2'); const submitButton = within(panel).getByRole('button', { name: '生成' }); expect(submitButton.textContent).toBe('生成5泥点'); }); it('keeps quick edit to one prompt box with image parameters', () => { const submitQuickEdit = vi.fn(); render( , ); const panel = screen.getByRole('dialog', { name: '快速编辑图片' }); const prompt = within(panel).getByRole('textbox', { name: '快速编辑提示词', }); expect(prompt.closest('.auto-grow-text-area')?.className).toContain( 'auto-grow-text-area', ); expect(prompt.className).not.toContain('platform-text-field'); fireEvent.keyDown(prompt, { key: 'Enter' }); expect(submitQuickEdit).not.toHaveBeenCalled(); expect( within(panel).queryByRole('button', { name: '添加参考图' }), ).toBeNull(); expect(within(panel).queryByText('旧参考图')).toBeNull(); expect( within(panel).getByRole('button', { name: '快速编辑图片尺寸 1:1·1K', }), ).toBeTruthy(); expect( within(panel).getByRole('button', { name: '快速编辑图片模型 gpt-image-2', }), ).toBeTruthy(); expect(within(panel).getByRole('button', { name: '修改' })).toBeTruthy(); }); it('clicks the parent generation panel to collapse an opened option panel', () => { render(); const panel = screen.getByRole('dialog', { name: '生成图片' }); fireEvent.click( within(panel).getByRole('button', { name: '生成图片尺寸 16:9·1K', }), ); expect(screen.getByRole('menu', { name: '生成图片尺寸选项' })).toBeTruthy(); fireEvent.click(screen.getByRole('textbox', { name: '生成提示词' })); expect(screen.queryByRole('menu', { name: '生成图片尺寸选项' })).toBeNull(); }); it('does not render a standalone close button', () => { render(); expect(screen.queryByRole('button', { name: '关闭生成图片' })).toBeNull(); }); });