aa67e72ec5
恢复画布布局中的 publication 生成器快照 补齐宣发素材卡片类型、游戏信息和参考图 hydrate 增加模型、面板和编辑器加载链路回归测试 更新图片画布持久化文档和共享踩坑记录
697 lines
24 KiB
TypeScript
697 lines
24 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
import {
|
|
type ComponentProps,
|
|
createRef,
|
|
type Dispatch,
|
|
type SetStateAction,
|
|
useState,
|
|
} from 'react';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { GenerateDialogState } from './ImageCanvasEditorTypes';
|
|
import { ImageCanvasGenerationComposerView } from './ImageCanvasGenerationComposerView';
|
|
|
|
function mockStateSetter<T>() {
|
|
return vi.fn() as unknown as Dispatch<SetStateAction<T>>;
|
|
}
|
|
|
|
|
|
function createComposerProps(
|
|
generateDialog: GenerateDialogState,
|
|
overrides: Partial<
|
|
ComponentProps<typeof ImageCanvasGenerationComposerView>
|
|
> = {},
|
|
): ComponentProps<typeof ImageCanvasGenerationComposerView> {
|
|
return {
|
|
specToolWrapRef: createRef(),
|
|
characterSpecButtonRef: createRef(),
|
|
characterReferenceButtonRef: createRef(),
|
|
generationReferenceButtonRef: createRef(),
|
|
iconSpecButtonRef: createRef(),
|
|
publicationReferenceButtonRef: createRef(),
|
|
isSpecMenuOpen: false,
|
|
isCharacterSpecMenuOpen: false,
|
|
isCharacterReferenceMenuOpen: false,
|
|
isGenerationReferenceMenuOpen: false,
|
|
isIconSpecMenuOpen: false,
|
|
isUiDesignSpecMenuOpen: false,
|
|
isPublicationReferenceMenuOpen: false,
|
|
isPickingCharacterSpecFromCanvas: false,
|
|
isPickingCharacterReferenceFromCanvas: false,
|
|
isPickingGenerationReferenceFromCanvas: false,
|
|
isPickingQuickEditReferenceFromCanvas: false,
|
|
isPickingIconSpecFromCanvas: false,
|
|
isPickingUiDesignSpecFromCanvas: false,
|
|
isPickingPublicationReferenceFromCanvas: false,
|
|
generateDialog,
|
|
generationComposerStyle: { left: 320, top: 240 },
|
|
iconComposerStyle: { left: 320, top: 240, width: '32rem' },
|
|
quickEditPanel: null,
|
|
quickEditSourceLayer: null,
|
|
quickEditPanelStyle: null,
|
|
cropExpandPanel: null,
|
|
cropExpandSourceLayer: null,
|
|
cropExpandPanelStyle: null,
|
|
characterAnimationPanel: null,
|
|
characterAnimationSourceLayer: null,
|
|
characterAnimationPanelStyle: null,
|
|
characterAnimationPrice: 40,
|
|
setGenerateDialog: mockStateSetter<GenerateDialogState | null>(),
|
|
setQuickEditPanel:
|
|
mockStateSetter<
|
|
ComponentProps<
|
|
typeof ImageCanvasGenerationComposerView
|
|
>['quickEditPanel']
|
|
>(),
|
|
setCropExpandPanel:
|
|
mockStateSetter<
|
|
ComponentProps<
|
|
typeof ImageCanvasGenerationComposerView
|
|
>['cropExpandPanel']
|
|
>(),
|
|
setCharacterAnimationPanel:
|
|
mockStateSetter<
|
|
ComponentProps<
|
|
typeof ImageCanvasGenerationComposerView
|
|
>['characterAnimationPanel']
|
|
>(),
|
|
setIsCharacterSpecMenuOpen: mockStateSetter<boolean>(),
|
|
setIsCharacterReferenceMenuOpen: mockStateSetter<boolean>(),
|
|
setIsGenerationReferenceMenuOpen: mockStateSetter<boolean>(),
|
|
setIsPickingQuickEditReferenceFromCanvas: mockStateSetter<boolean>(),
|
|
setIsIconSpecMenuOpen: mockStateSetter<boolean>(),
|
|
setIsUiDesignSpecMenuOpen: mockStateSetter<boolean>(),
|
|
setIsPublicationReferenceMenuOpen: mockStateSetter<boolean>(),
|
|
setIsPickingCharacterSpecFromCanvas: mockStateSetter<boolean>(),
|
|
setIsPickingCharacterReferenceFromCanvas: mockStateSetter<boolean>(),
|
|
setIsPickingGenerationReferenceFromCanvas: mockStateSetter<boolean>(),
|
|
setIsPickingIconSpecFromCanvas: mockStateSetter<boolean>(),
|
|
setIsPickingUiDesignSpecFromCanvas: mockStateSetter<boolean>(),
|
|
setIsPickingPublicationReferenceFromCanvas: mockStateSetter<boolean>(),
|
|
onOpenSpecDialog: vi.fn(),
|
|
onRequestUpload: vi.fn(),
|
|
onSubmitImageGeneration: vi.fn(),
|
|
onSubmitIconSpritesheetGeneration: vi.fn(),
|
|
onSubmitQuickEdit: vi.fn(),
|
|
onSubmitCropExpand: vi.fn(),
|
|
onSubmitCharacterAnimation: vi.fn(),
|
|
onUpdateSpecFormValue: vi.fn(),
|
|
onUpdateIconDescriptionText: vi.fn(),
|
|
onUpdateCharacterAnimationDuration: vi.fn(),
|
|
onRememberImageModel: vi.fn(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function renderComposer(
|
|
generateDialog: GenerateDialogState,
|
|
overrides: Partial<
|
|
ComponentProps<typeof ImageCanvasGenerationComposerView>
|
|
> = {},
|
|
) {
|
|
const props = createComposerProps(generateDialog, overrides);
|
|
|
|
return render(<ImageCanvasGenerationComposerView {...props} />);
|
|
}
|
|
|
|
describe('ImageCanvasGenerationComposerView', () => {
|
|
it('让生成UI设计图面板复用普通图片生成面板的纵向结构', () => {
|
|
const setGenerateDialog = vi.fn();
|
|
renderComposer({
|
|
mode: 'ui-design',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
uiDesignSpecReference: null,
|
|
imageModel: 'gpt-image-2',
|
|
aspectRatio: '16:9',
|
|
imageSize: '1K',
|
|
}, {
|
|
isUiDesignSpecMenuOpen: true,
|
|
setGenerateDialog:
|
|
setGenerateDialog as unknown as Dispatch<
|
|
SetStateAction<GenerateDialogState | null>
|
|
>,
|
|
});
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成UI设计图' });
|
|
expect(
|
|
panel.firstElementChild?.className.includes(
|
|
'image-canvas-editor__reference-strip',
|
|
),
|
|
).toBe(true);
|
|
expect(panel.className).toContain(
|
|
'image-canvas-editor__generation-composer',
|
|
);
|
|
expect(panel.className).toContain(
|
|
'image-canvas-editor__generation-composer--image',
|
|
);
|
|
expect(
|
|
within(panel).getByRole('button', { name: 'UI设计图标规范' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(panel).getByRole('textbox', { name: 'UI设计要求' }).className,
|
|
).toContain('image-canvas-editor__generation-prompt');
|
|
expect(
|
|
panel.querySelector('.image-canvas-editor__generation-composer-footer'),
|
|
).toBeTruthy();
|
|
fireEvent.change(within(panel).getByRole('textbox', { name: 'UI设计要求' }), {
|
|
target: { value: '主界面和结算弹窗' },
|
|
});
|
|
expect(setGenerateDialog).toHaveBeenCalled();
|
|
const menu = screen.getByRole('menu', { name: '参考图来源' });
|
|
expect(
|
|
within(menu).getByRole('menuitem', { name: '新建图标规范' }),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it('让生成规范图片面板复用生成类面板 shell 和底部按钮结构', () => {
|
|
renderComposer({
|
|
mode: 'spec',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
specType: 'character',
|
|
specValues: {
|
|
playSetting: '战棋类RPG玩法',
|
|
artStyle: '像素风',
|
|
bodyRatio: '3',
|
|
characterView: '右向斜侧身站姿',
|
|
customPrompt: '',
|
|
},
|
|
specReference: null,
|
|
});
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成规范' });
|
|
expect(
|
|
panel.firstElementChild?.className.includes(
|
|
'image-canvas-editor__reference-strip',
|
|
),
|
|
).toBe(true);
|
|
expect(panel.className).toContain(
|
|
'image-canvas-editor__generation-composer',
|
|
);
|
|
expect(panel.className).toContain(
|
|
'image-canvas-editor__generation-composer--image',
|
|
);
|
|
expect(within(panel).getByRole('button', { name: '参考图' })).toBeTruthy();
|
|
expect(
|
|
panel.querySelector('.image-canvas-editor__generation-composer-footer'),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(panel).getByRole('button', { name: '提交生成规范' }).className,
|
|
).toContain('image-canvas-editor__generation-submit');
|
|
});
|
|
|
|
it('让图标规范生成面板也保留首行参考区', () => {
|
|
renderComposer({
|
|
mode: 'spec',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
specType: 'icon',
|
|
specValues: {
|
|
playSetting: '休闲小游戏',
|
|
artStyle: '清爽卡通',
|
|
bodyRatio: '3',
|
|
characterView: '右向斜侧身站姿',
|
|
customPrompt: '',
|
|
},
|
|
specReference: null,
|
|
});
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成规范' });
|
|
expect(
|
|
panel.firstElementChild?.className.includes(
|
|
'image-canvas-editor__reference-strip',
|
|
),
|
|
).toBe(true);
|
|
expect(within(panel).getByRole('button', { name: '参考图' })).toBeTruthy();
|
|
});
|
|
|
|
it.each([
|
|
['publication-cover-image', '游戏首图', '720 x 540'],
|
|
['publication-detail-gallery', '详情五图', '720 x 1280'],
|
|
['publication-promo-poster', '运营海报', '1280 x 720'],
|
|
] as const)('让%s 宣发素材面板对齐生成角色面板结构', (workflowId, label, sizeLabel) => {
|
|
renderComposer({
|
|
mode: 'publication',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
publicationWorkflowId: workflowId,
|
|
publicationGameInfo: {
|
|
gameName: '',
|
|
gameCategories: '',
|
|
gameDescription: '',
|
|
},
|
|
publicationReferences: [],
|
|
imageModel: 'gpt-image-2',
|
|
aspectRatio: '16:9',
|
|
imageSize: '1K',
|
|
});
|
|
|
|
const panel = screen.getByRole('dialog', { name: `${label}生成卡片` });
|
|
expect(panel.className).toContain('image-canvas-editor__character-composer');
|
|
expect(panel.className).toContain('image-canvas-editor__publication-composer');
|
|
expect(
|
|
panel.firstElementChild?.className.includes(
|
|
'image-canvas-editor__reference-strip',
|
|
),
|
|
).toBe(true);
|
|
expect(within(panel).getByRole('button', { name: '添加参考图' })).toBeTruthy();
|
|
expect(
|
|
within(panel).getByRole('textbox', { name: `${label}游戏名` }),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(panel).getByRole('textbox', { name: `${label}一句话描述游戏` }),
|
|
).toBeTruthy();
|
|
expect(panel.textContent).toContain(sizeLabel);
|
|
expect(
|
|
panel.querySelector('.image-canvas-editor__generation-composer-footer'),
|
|
).toBeTruthy();
|
|
expect(within(panel).getByRole('button', { name: '生成' }).textContent).toBe(
|
|
'生成20泥点',
|
|
);
|
|
});
|
|
|
|
it('恢复宣发素材生成卡片的字段和参考图', () => {
|
|
renderComposer({
|
|
mode: 'publication',
|
|
prompt: '马戏团午夜惊魂|非对称对抗|找到钥匙逃离',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
publicationWorkflowId: 'publication-promo-poster',
|
|
publicationGameInfo: {
|
|
gameName: '马戏团午夜惊魂',
|
|
gameCategories: '非对称对抗',
|
|
gameDescription: '找到钥匙,开门逃离马戏团',
|
|
},
|
|
publicationReferences: [
|
|
{
|
|
id: 'publication-reference-1',
|
|
label: '首图参考',
|
|
src: '/generated-character-drafts/editor/reference.png',
|
|
},
|
|
],
|
|
imageModel: 'gpt-image-2',
|
|
aspectRatio: '16:9',
|
|
imageSize: '2K',
|
|
});
|
|
|
|
const panel = screen.getByRole('dialog', { name: '运营海报生成卡片' });
|
|
expect(
|
|
(within(panel).getByRole('textbox', {
|
|
name: '运营海报游戏名',
|
|
}) as HTMLInputElement).value,
|
|
).toBe('马戏团午夜惊魂');
|
|
expect(
|
|
(within(panel).getByRole('textbox', {
|
|
name: '运营海报游戏分类',
|
|
}) as HTMLInputElement).value,
|
|
).toBe('非对称对抗');
|
|
expect(
|
|
(within(panel).getByRole('textbox', {
|
|
name: '运营海报一句话描述游戏',
|
|
}) as HTMLTextAreaElement).value,
|
|
).toBe('找到钥匙,开门逃离马戏团');
|
|
expect(within(panel).getByLabelText('首图参考')).toBeTruthy();
|
|
expect(
|
|
within(panel).getByRole('button', { name: '删除首图参考' }),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it('生成图片参考图点击先弹来源菜单,不直接打开上传', () => {
|
|
const onRequestUpload = vi.fn();
|
|
const setIsGenerationReferenceMenuOpen = vi.fn();
|
|
|
|
renderComposer(
|
|
{
|
|
mode: 'generate',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
generationReferences: [],
|
|
},
|
|
{
|
|
isGenerationReferenceMenuOpen: true,
|
|
onRequestUpload,
|
|
setIsGenerationReferenceMenuOpen,
|
|
},
|
|
);
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成图片' });
|
|
fireEvent.click(within(panel).getByRole('button', { name: '添加参考图' }));
|
|
|
|
expect(onRequestUpload).not.toHaveBeenCalled();
|
|
expect(setIsGenerationReferenceMenuOpen).toHaveBeenCalled();
|
|
const menu = screen.getByRole('menu', { name: '参考图来源' });
|
|
expect(
|
|
within(menu).getByRole('menuitem', { name: '从画布中选择' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(menu).getByRole('menuitem', { name: '上传图片' }),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it('生成视频参考图点击先弹来源菜单,不直接打开上传', () => {
|
|
const onRequestUpload = vi.fn();
|
|
const setIsGenerationReferenceMenuOpen = vi.fn();
|
|
|
|
renderComposer(
|
|
{
|
|
mode: 'video',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
generationReferences: [],
|
|
videoModel: 'seedance2.0-fast',
|
|
videoAspectRatio: '16:9',
|
|
videoDurationSeconds: 4,
|
|
videoResolution: '480p',
|
|
videoMode: 'std',
|
|
videoSound: 'off',
|
|
},
|
|
{
|
|
isGenerationReferenceMenuOpen: true,
|
|
onRequestUpload,
|
|
setIsGenerationReferenceMenuOpen,
|
|
},
|
|
);
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成视频' });
|
|
fireEvent.click(
|
|
within(panel).getByRole('button', { name: '添加视频参考素材' }),
|
|
);
|
|
|
|
expect(onRequestUpload).not.toHaveBeenCalled();
|
|
expect(setIsGenerationReferenceMenuOpen).toHaveBeenCalled();
|
|
const menu = screen.getByRole('menu', { name: '参考素材来源' });
|
|
expect(
|
|
within(menu).getByRole('menuitem', { name: '从画布中选择' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(menu).getByRole('menuitem', { name: '上传图片' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(menu).getByRole('menuitem', { name: '上传视频' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(menu).getByRole('menuitem', { name: '上传音频' }),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it('点击生成视频父级面板任意区域会收起参考素材菜单', () => {
|
|
function VideoReferenceMenuHarness() {
|
|
const [isGenerationReferenceMenuOpen, setIsGenerationReferenceMenuOpen] =
|
|
useState(true);
|
|
const dialog: GenerateDialogState = {
|
|
mode: 'video',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
generationReferences: [],
|
|
videoModel: 'seedance2.0-fast',
|
|
videoAspectRatio: '16:9',
|
|
videoDurationSeconds: 4,
|
|
videoResolution: '480p',
|
|
videoMode: 'std',
|
|
videoSound: 'off',
|
|
};
|
|
|
|
return (
|
|
<ImageCanvasGenerationComposerView
|
|
{...createComposerProps(dialog)}
|
|
isGenerationReferenceMenuOpen={isGenerationReferenceMenuOpen}
|
|
setIsGenerationReferenceMenuOpen={setIsGenerationReferenceMenuOpen}
|
|
/>
|
|
);
|
|
}
|
|
|
|
render(<VideoReferenceMenuHarness />);
|
|
|
|
expect(screen.getByRole('menu', { name: '参考素材来源' })).toBeTruthy();
|
|
|
|
fireEvent.click(screen.getByRole('textbox', { name: '视频描述' }));
|
|
|
|
expect(screen.queryByRole('menu', { name: '参考素材来源' })).toBeNull();
|
|
});
|
|
|
|
it('生成视频面板用子面板修改参数和模型,不展示 Veo,并在按钮内显示泥点文案', () => {
|
|
function VideoHarness() {
|
|
const [dialog, setDialog] = useState<GenerateDialogState>({
|
|
mode: 'video',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
generationReferences: [],
|
|
videoModel: 'seedance2.0-fast',
|
|
videoAspectRatio: '16:9',
|
|
videoDurationSeconds: 4,
|
|
videoResolution: '480p',
|
|
videoMode: 'std',
|
|
videoSound: 'off',
|
|
placeholder: {
|
|
x: 53,
|
|
y: 120,
|
|
width: 854,
|
|
height: 480,
|
|
originalWidth: 854,
|
|
originalHeight: 480,
|
|
},
|
|
});
|
|
return (
|
|
<>
|
|
<ImageCanvasGenerationComposerView
|
|
{...createComposerProps(dialog)}
|
|
setGenerateDialog={setDialog as Dispatch<SetStateAction<GenerateDialogState | null>>}
|
|
/>
|
|
<output aria-label="当前视频模型">{dialog.videoModel}</output>
|
|
<output aria-label="当前视频时长">{dialog.videoDurationSeconds}</output>
|
|
<output aria-label="当前视频清晰度">{dialog.videoResolution}</output>
|
|
<output aria-label="当前视频占位">
|
|
{dialog.placeholder
|
|
? [
|
|
dialog.placeholder.x,
|
|
dialog.placeholder.y,
|
|
dialog.placeholder.width,
|
|
dialog.placeholder.height,
|
|
dialog.placeholder.originalWidth,
|
|
dialog.placeholder.originalHeight,
|
|
].join(':')
|
|
: '-'}
|
|
</output>
|
|
</>
|
|
);
|
|
}
|
|
|
|
render(<VideoHarness />);
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成视频' });
|
|
expect(
|
|
within(panel).getByRole('button', { name: '视频参数 16:9 · 4秒 · 480p' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(panel).getByRole('button', { name: '模型 Seedance 2.0 Fast' }),
|
|
).toBeTruthy();
|
|
expect(within(panel).getByRole('button', { name: '生成视频' }).textContent).toBe(
|
|
'生成40泥点',
|
|
);
|
|
|
|
fireEvent.click(
|
|
within(panel).getByRole('button', { name: '视频参数 16:9 · 4秒 · 480p' }),
|
|
);
|
|
const paramsPanel = screen.getByRole('menu', {
|
|
name: '视频参数选项',
|
|
});
|
|
fireEvent.click(within(paramsPanel).getByRole('button', { name: '时长 5秒' }));
|
|
fireEvent.click(within(paramsPanel).getByRole('button', { name: '清晰度 720p' }));
|
|
|
|
expect(
|
|
screen.getByRole('menu', { name: '视频参数选项' }),
|
|
).toBeTruthy();
|
|
expect(screen.getByLabelText('当前视频时长').textContent).toBe('5');
|
|
expect(screen.getByLabelText('当前视频清晰度').textContent).toBe('720p');
|
|
expect(screen.getByLabelText('当前视频占位').textContent).toBe(
|
|
'-160:0:1280:720:1280:720',
|
|
);
|
|
expect(screen.getByRole('button', { name: '视频参数 16:9 · 5秒 · 720p' })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '生成视频' }).textContent).toBe(
|
|
'生成100泥点',
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '模型 Seedance 2.0 Fast' }));
|
|
const modelPanel = screen.getByRole('menu', { name: '视频模型选项' });
|
|
expect(within(modelPanel).queryByRole('button', { name: /Veo/i })).toBeNull();
|
|
fireEvent.click(within(modelPanel).getByRole('button', { name: 'Kling 3.0' }));
|
|
|
|
expect(screen.getByRole('menu', { name: '视频模型选项' })).toBeTruthy();
|
|
expect(screen.getByLabelText('当前视频模型').textContent).toBe('kling3.0');
|
|
expect(screen.getByRole('button', { name: '模型 Kling 3.0' })).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '生成视频' }).textContent).toBe(
|
|
'生成150泥点',
|
|
);
|
|
});
|
|
|
|
it('生成游戏音效面板使用 Vidu duration 参数且不再显示类型和 BPM', () => {
|
|
const onSubmitImageGeneration = vi.fn();
|
|
function AudioHarness() {
|
|
const [dialog, setDialog] = useState<GenerateDialogState>({
|
|
mode: 'audio-sound-effect',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
soundModel: 'audio1.0',
|
|
soundDurationSeconds: 5,
|
|
});
|
|
return (
|
|
<>
|
|
<ImageCanvasGenerationComposerView
|
|
{...createComposerProps(dialog, { onSubmitImageGeneration })}
|
|
setGenerateDialog={
|
|
setDialog as Dispatch<SetStateAction<GenerateDialogState | null>>
|
|
}
|
|
/>
|
|
<output aria-label="当前音效模型">{dialog.soundModel}</output>
|
|
<output aria-label="当前音效时长">
|
|
{dialog.soundDurationSeconds ?? 'null'}
|
|
</output>
|
|
</>
|
|
);
|
|
}
|
|
|
|
render(<AudioHarness />);
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成游戏音效' });
|
|
const footer = panel.querySelector(
|
|
'.image-canvas-editor__generation-composer-footer',
|
|
);
|
|
expect(footer).toBeTruthy();
|
|
expect(footer?.children[0]?.className).toContain(
|
|
'image-canvas-editor__option-popover-anchor--dimensions',
|
|
);
|
|
expect(footer?.children[1]?.className).toContain(
|
|
'image-canvas-editor__option-popover-anchor--model',
|
|
);
|
|
expect(footer?.children[2]?.className).toContain(
|
|
'image-canvas-editor__generation-submit',
|
|
);
|
|
expect(within(panel).getByRole('textbox', { name: 'prompt' })).toBeTruthy();
|
|
expect(screen.getByLabelText('当前音效模型').textContent).toBe('audio1.0');
|
|
const soundModelButton = within(panel).getByRole('button', {
|
|
name: '音效模型 Vidu',
|
|
});
|
|
expect((soundModelButton as HTMLButtonElement).disabled).toBe(true);
|
|
expect(within(panel).getByText('Vidu')).toBeTruthy();
|
|
expect(within(panel).queryByText(/Suno/u)).toBeNull();
|
|
expect(
|
|
within(panel).getByRole('button', { name: '音效时长 5秒' }),
|
|
).toBeTruthy();
|
|
expect(within(panel).queryByText('单次')).toBeNull();
|
|
expect(within(panel).queryByText('循环')).toBeNull();
|
|
expect(within(panel).queryByText(/BPM/u)).toBeNull();
|
|
expect(within(panel).getByRole('button', { name: '生成游戏音效' }).textContent).toBe(
|
|
'生成10泥点',
|
|
);
|
|
|
|
fireEvent.click(
|
|
within(panel).getByRole('button', { name: '音效时长 5秒' }),
|
|
);
|
|
const optionPanel = screen.getByRole('menu', { name: '音效时长选项' });
|
|
expect(
|
|
within(optionPanel).getByRole('button', { name: '时长 2秒' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
within(optionPanel).getByRole('button', { name: '时长 10秒' }),
|
|
).toBeTruthy();
|
|
expect(within(optionPanel).queryByText(/BPM/u)).toBeNull();
|
|
fireEvent.click(within(optionPanel).getByRole('button', { name: '时长 8秒' }));
|
|
|
|
expect(screen.getByLabelText('当前音效时长').textContent).toBe('8');
|
|
expect(
|
|
screen.getByRole('button', { name: '音效时长 8秒' }),
|
|
).toBeTruthy();
|
|
|
|
fireEvent.submit(panel);
|
|
expect(onSubmitImageGeneration).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
mode: 'audio-sound-effect',
|
|
soundDurationSeconds: 8,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('生成游戏背景音乐面板只展示 gpt_description_prompt 并固定隐藏 instrumental 开关', () => {
|
|
renderComposer({
|
|
mode: 'audio-background-music',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
});
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成游戏背景音乐' });
|
|
expect(
|
|
within(panel).getByRole('textbox', { name: 'gpt_description_prompt' }),
|
|
).toBeTruthy();
|
|
const footer = panel.querySelector(
|
|
'.image-canvas-editor__generation-composer-footer',
|
|
);
|
|
expect(footer).toBeTruthy();
|
|
expect(footer?.children[0]?.className).toContain(
|
|
'image-canvas-editor__option-popover-anchor--model',
|
|
);
|
|
expect(footer?.children[1]?.className).toContain(
|
|
'image-canvas-editor__generation-submit',
|
|
);
|
|
const backgroundMusicModelButton = within(panel).getByRole('button', {
|
|
name: '背景音乐模型 Suno',
|
|
});
|
|
expect((backgroundMusicModelButton as HTMLButtonElement).disabled).toBe(
|
|
true,
|
|
);
|
|
expect(within(panel).getByText('Suno')).toBeTruthy();
|
|
expect(within(panel).queryByText('make_instrumental')).toBeNull();
|
|
expect(within(panel).getByRole('button', { name: '生成游戏背景音乐' }).textContent).toBe(
|
|
'生成5泥点',
|
|
);
|
|
});
|
|
it('生成规范参考图点击先弹来源菜单,不直接打开上传', () => {
|
|
const onRequestUpload = vi.fn();
|
|
const setIsGenerationReferenceMenuOpen = vi.fn();
|
|
|
|
renderComposer(
|
|
{
|
|
mode: 'spec',
|
|
prompt: '',
|
|
status: 'idle',
|
|
composerOpen: true,
|
|
specType: 'character',
|
|
specValues: {
|
|
playSetting: '战棋类RPG玩法',
|
|
artStyle: '像素风',
|
|
bodyRatio: '3',
|
|
characterView: '右向斜侧身站姿',
|
|
customPrompt: '',
|
|
},
|
|
specReference: null,
|
|
},
|
|
{
|
|
isGenerationReferenceMenuOpen: true,
|
|
onRequestUpload,
|
|
setIsGenerationReferenceMenuOpen,
|
|
},
|
|
);
|
|
|
|
const panel = screen.getByRole('dialog', { name: '生成规范' });
|
|
fireEvent.click(within(panel).getByRole('button', { name: '参考图' }));
|
|
|
|
expect(onRequestUpload).not.toHaveBeenCalled();
|
|
expect(setIsGenerationReferenceMenuOpen).toHaveBeenCalled();
|
|
const menu = screen.getByRole('menu', { name: '参考图来源' });
|
|
expect(within(menu).getByRole('menuitem', { name: '从画布中选择' })).toBeTruthy();
|
|
expect(within(menu).getByRole('menuitem', { name: '上传图片' })).toBeTruthy();
|
|
});
|
|
});
|