676bd524ee
示例来自微信:  before: - firefox:  - edge:  after: - firefox:   - edge:  - 使用 Lexical + OverlayScrollbars 实现统一可控的滑动条样式, 取代原来的textarea Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/149 Co-authored-by: 王德宇 <kvtodev@outlook.com> Co-committed-by: 王德宇 <kvtodev@outlook.com>
263 lines
8.9 KiB
TypeScript
263 lines
8.9 KiB
TypeScript
/* @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> = {},
|
|
): 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<GenerateDialogState | null>(
|
|
initialDialog,
|
|
);
|
|
const [isReferenceMenuOpen, setIsReferenceMenuOpen] = useState(false);
|
|
const [isPickingReference, setIsPickingReference] = useState(false);
|
|
const generationReferenceButtonRef = createRef<HTMLButtonElement>();
|
|
|
|
return dialog ? (
|
|
<div>
|
|
<ImageCanvasBasicGenerationComposerView
|
|
dialog={dialog}
|
|
style={{ left: 10, top: 20 }}
|
|
setGenerateDialog={setDialog}
|
|
generationReferenceButtonRef={generationReferenceButtonRef}
|
|
isGenerationReferenceMenuOpen={isReferenceMenuOpen}
|
|
setIsGenerationReferenceMenuOpen={setIsReferenceMenuOpen}
|
|
setIsPickingGenerationReferenceFromCanvas={setIsPickingReference}
|
|
renderEditorPortal={(node) => node}
|
|
buildPortalMenuStyle={() => ({ position: 'fixed', left: 0, top: 0 })}
|
|
onRequestUpload={onRequestUpload}
|
|
onToggleReferenceMenu={() => setIsReferenceMenuOpen((open) => !open)}
|
|
onSubmit={onSubmit}
|
|
/>
|
|
<output aria-label="当前提示词">{dialog.prompt}</output>
|
|
<output aria-label="当前比例">{dialog.aspectRatio}</output>
|
|
<output aria-label="当前尺寸">{dialog.imageSize}</output>
|
|
<output aria-label="当前模型">{dialog.imageModel}</output>
|
|
<output aria-label="当前状态">{dialog.status}</output>
|
|
<output aria-label="当前错误">{dialog.errorMessage ?? '-'}</output>
|
|
<output aria-label="选择参考图">{String(isPickingReference)}</output>
|
|
</div>
|
|
) : null;
|
|
}
|
|
|
|
describe('ImageCanvasBasicGenerationComposerView', () => {
|
|
it('updates prompt, clears failed state, uploads references and submits', () => {
|
|
const requestUpload = vi.fn();
|
|
const submitGeneration = vi.fn();
|
|
render(
|
|
<BasicGenerationHarness
|
|
initialDialog={createDialog({
|
|
status: 'failed',
|
|
errorMessage: '生成失败',
|
|
})}
|
|
onRequestUpload={requestUpload}
|
|
onSubmit={submitGeneration}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<BasicGenerationHarness
|
|
initialDialog={createDialog({
|
|
generationReferences: [
|
|
{
|
|
id: 'ref-a',
|
|
label: '参考图A',
|
|
src: 'data:image/png;base64,cmVmQQ==',
|
|
},
|
|
{
|
|
id: 'ref-b',
|
|
label: '参考图B',
|
|
src: 'data:image/png;base64,cmVmQg==',
|
|
},
|
|
],
|
|
})}
|
|
/>,
|
|
);
|
|
|
|
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(
|
|
<BasicGenerationHarness
|
|
initialDialog={createDialog({
|
|
generationReferences: [
|
|
{
|
|
id: 'ref-a',
|
|
label: '参考图A',
|
|
src: 'data:image/png;base64,cmVmQQ==',
|
|
},
|
|
],
|
|
})}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '删除参考图A' }));
|
|
|
|
expect(screen.queryByText('参考图A')).toBeNull();
|
|
});
|
|
|
|
it('shows only current image options and opens upward option panels', () => {
|
|
render(<BasicGenerationHarness />);
|
|
|
|
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(
|
|
<BasicGenerationHarness
|
|
initialDialog={createDialog({
|
|
mode: 'quick-edit',
|
|
sourceLayerId: 'layer-source',
|
|
generationReferences: [
|
|
{
|
|
id: 'ref-a',
|
|
label: '旧参考图',
|
|
src: 'data:image/png;base64,cmVmQQ==',
|
|
},
|
|
],
|
|
imageModel: 'gpt-image-2',
|
|
aspectRatio: '1:1',
|
|
imageSize: '1K',
|
|
})}
|
|
onSubmit={submitQuickEdit}
|
|
/>,
|
|
);
|
|
|
|
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(<BasicGenerationHarness />);
|
|
|
|
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(<BasicGenerationHarness />);
|
|
|
|
expect(screen.queryByRole('button', { name: '关闭生成图片' })).toBeNull();
|
|
});
|
|
});
|