4a12b090b5
音效、背景音乐和图标素材改为空值加灰色启发提示。 角色规范、图标规范和自定义规范输入框补充灰色启发提示。 同步更新相关前端组件测试。
319 lines
9.9 KiB
TypeScript
319 lines
9.9 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
|
|
import { createRef, useState } from 'react';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import type {
|
|
GenerateDialogState,
|
|
SpecFormValues,
|
|
UploadTarget,
|
|
} from './ImageCanvasEditorTypes';
|
|
import { ImageCanvasSpecGenerationPanelView } from './ImageCanvasSpecGenerationPanelView';
|
|
|
|
function createSpecDialog(
|
|
patch: Partial<GenerateDialogState> = {},
|
|
): 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(
|
|
<ImageCanvasSpecGenerationPanelView
|
|
dialog={dialog}
|
|
style={{ left: 10, top: 20 }}
|
|
onUpdateSpecFormValue={onUpdateSpecFormValue}
|
|
onRequestUpload={onRequestUpload}
|
|
onSubmit={onSubmit}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
function UiDesignHarness({
|
|
initialDialog,
|
|
}: {
|
|
initialDialog: GenerateDialogState;
|
|
}) {
|
|
const [dialog, setDialog] = useState<GenerateDialogState | null>(
|
|
initialDialog,
|
|
);
|
|
const referenceButtonRef = createRef<HTMLButtonElement>();
|
|
|
|
return dialog ? (
|
|
<ImageCanvasSpecGenerationPanelView
|
|
dialog={dialog}
|
|
style={{ left: 10, top: 20 }}
|
|
generationReferenceButtonRef={referenceButtonRef}
|
|
setGenerateDialog={setDialog}
|
|
renderEditorPortal={(node) => node}
|
|
buildPortalMenuStyle={() => ({ position: 'fixed', left: 0, top: 0 })}
|
|
onUpdateSpecFormValue={vi.fn()}
|
|
onRequestUpload={vi.fn()}
|
|
onSubmit={vi.fn()}
|
|
/>
|
|
) : null;
|
|
}
|
|
|
|
describe('ImageCanvasSpecGenerationPanelView', () => {
|
|
it('keeps the reference row above fields for spec panels and shows mud point text', () => {
|
|
renderPanel({ dialog: createSpecDialog() });
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成规范' });
|
|
expect(
|
|
panel.firstElementChild?.className.includes(
|
|
'image-canvas-editor__reference-strip',
|
|
),
|
|
).toBe(true);
|
|
const submitButton = screen.getByRole('button', { name: '提交生成规范' });
|
|
expect(submitButton.textContent).toBe('生成5泥点');
|
|
const dimensionsButton = screen.getByRole('button', {
|
|
name: '生成图片尺寸 16:9·2K',
|
|
}) as HTMLButtonElement;
|
|
const modelButton = screen.getByRole('button', {
|
|
name: '生成图片模型 gpt-image-2',
|
|
}) as HTMLButtonElement;
|
|
|
|
expect(dimensionsButton.disabled).toBe(true);
|
|
expect(modelButton.disabled).toBe(true);
|
|
expect(dimensionsButton.className).toContain(
|
|
'image-canvas-editor__option-cluster--dimensions',
|
|
);
|
|
expect(modelButton.className).toContain(
|
|
'image-canvas-editor__option-cluster--model',
|
|
);
|
|
fireEvent.click(dimensionsButton);
|
|
fireEvent.click(modelButton);
|
|
expect(screen.queryByRole('menu', { name: '生成图片尺寸选项' })).toBeNull();
|
|
expect(screen.queryByRole('menu', { name: '生成图片模型选项' })).toBeNull();
|
|
});
|
|
|
|
it('renders UI design as a single borderless prompt below the first reference row', () => {
|
|
render(
|
|
<UiDesignHarness
|
|
initialDialog={{
|
|
mode: 'ui-design',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
uiDesignSpecReference: null,
|
|
imageModel: 'nanobanana2',
|
|
aspectRatio: '16:9',
|
|
imageSize: '1K',
|
|
}}
|
|
/>,
|
|
);
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成UI设计图' });
|
|
const prompt = screen.getByRole('textbox', { name: 'UI设计要求' });
|
|
|
|
expect(
|
|
panel.firstElementChild?.className.includes(
|
|
'image-canvas-editor__reference-strip',
|
|
),
|
|
).toBe(true);
|
|
expect(panel.textContent).not.toContain('UI设计要求');
|
|
expect(prompt.getAttribute('placeholder')).toBe('你希望这个 UI 长什么样?');
|
|
expect(prompt.className).toContain(
|
|
'image-canvas-editor__generation-prompt--borderless',
|
|
);
|
|
const submitButton = screen.getByRole('button', { name: '生成UI设计图' });
|
|
const modelButton = screen.getByRole('button', {
|
|
name: '生成图片模型 gpt-image-2',
|
|
}) as HTMLButtonElement;
|
|
|
|
expect(submitButton.textContent).toBe('生成3泥点');
|
|
expect(modelButton.disabled).toBe(true);
|
|
expect(modelButton.closest('div')?.className).toContain(
|
|
'image-canvas-editor__readonly-generation-option',
|
|
);
|
|
fireEvent.click(modelButton);
|
|
expect(screen.queryByRole('menu', { name: '生成图片模型选项' })).toBeNull();
|
|
expect(screen.queryByText('nanobanana2')).toBeNull();
|
|
});
|
|
|
|
it('renders character spec fields and forwards updates', () => {
|
|
const updateSpecFormValue = vi.fn();
|
|
renderPanel({
|
|
dialog: createSpecDialog(),
|
|
onUpdateSpecFormValue: updateSpecFormValue,
|
|
});
|
|
|
|
expect(
|
|
(screen.getByLabelText('玩法设定') as HTMLInputElement).placeholder,
|
|
).toBe('这是什么类型的游戏?');
|
|
expect(
|
|
(screen.getByLabelText('美术风格') as HTMLInputElement).placeholder,
|
|
).toBe('游戏的画风是怎样的?');
|
|
|
|
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('uses gameplay and art style hints for icon specs', () => {
|
|
renderPanel({
|
|
dialog: createSpecDialog({
|
|
specType: 'ui',
|
|
specValues: {
|
|
playSetting: '',
|
|
artStyle: '',
|
|
bodyRatio: '3',
|
|
characterView: '右向斜侧身站姿',
|
|
customPrompt: '',
|
|
},
|
|
}),
|
|
});
|
|
|
|
expect((screen.getByLabelText('玩法设定') as HTMLInputElement).value).toBe(
|
|
'',
|
|
);
|
|
expect(
|
|
(screen.getByLabelText('玩法设定') as HTMLInputElement).placeholder,
|
|
).toBe('这是什么类型的游戏?');
|
|
expect((screen.getByLabelText('美术风格') as HTMLInputElement).value).toBe(
|
|
'',
|
|
);
|
|
expect(
|
|
(screen.getByLabelText('美术风格') as HTMLInputElement).placeholder,
|
|
).toBe('游戏的画风是怎样的?');
|
|
});
|
|
|
|
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,
|
|
});
|
|
|
|
const customPrompt = screen.getByLabelText(
|
|
'自定义规范提示词',
|
|
) as HTMLTextAreaElement;
|
|
expect(customPrompt.placeholder).toBe(
|
|
'写下你想要的任何内容,陶泥儿帮你形成新的约束',
|
|
);
|
|
|
|
fireEvent.change(screen.getByLabelText('自定义规范提示词'), {
|
|
target: { value: '新的规范提示' },
|
|
});
|
|
|
|
expect(updateSpecFormValue).toHaveBeenCalledWith(
|
|
'customPrompt',
|
|
'新的规范提示',
|
|
);
|
|
|
|
cleanup();
|
|
renderPanel({ dialog: createSpecDialog({ specType: 'icon' }) });
|
|
|
|
expect(screen.getByRole('button', { name: '参考图' })).toBeTruthy();
|
|
});
|
|
|
|
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('renders compact spec submit cost with mud point text', () => {
|
|
renderPanel({ dialog: createSpecDialog() });
|
|
|
|
const submitButton = screen.getByRole('button', { name: '提交生成规范' });
|
|
|
|
expect(submitButton.textContent).toBe('生成5泥点');
|
|
});
|
|
|
|
it('disables controls while generating and renders failure state', () => {
|
|
const submitSpec = vi.fn();
|
|
const { rerender } = render(
|
|
<ImageCanvasSpecGenerationPanelView
|
|
dialog={createSpecDialog({ status: 'generating' })}
|
|
style={{ left: 10, top: 20 }}
|
|
onUpdateSpecFormValue={vi.fn()}
|
|
onRequestUpload={vi.fn()}
|
|
onSubmit={submitSpec}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '提交生成规范' }));
|
|
|
|
expect(
|
|
(screen.getByLabelText('玩法设定') as HTMLInputElement).disabled,
|
|
).toBe(true);
|
|
expect(submitSpec).not.toHaveBeenCalled();
|
|
|
|
rerender(
|
|
<ImageCanvasSpecGenerationPanelView
|
|
dialog={createSpecDialog({
|
|
status: 'failed',
|
|
errorMessage: '生成失败',
|
|
})}
|
|
style={{ left: 10, top: 20 }}
|
|
onUpdateSpecFormValue={vi.fn()}
|
|
onRequestUpload={vi.fn()}
|
|
onSubmit={submitSpec}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByRole('alert').textContent).toContain('生成失败');
|
|
});
|
|
});
|