ac180ecad9
新增画板底部生成音乐入口,支持游戏音效和游戏背景音乐生成面板。 接入编辑器音频生成前后端契约、VectorEngine 请求与 BFF 路由。 补齐音频结果图层、元数据、画布恢复和客户端提交流程。 统一生成类面板 Lovart 式参数浮层、参考素材入口与相关测试。 更新编辑器音乐生成设计文档和项目长期记忆。
178 lines
5.7 KiB
TypeScript
178 lines
5.7 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
import { useState } from 'react';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { GenerateDialogState } from './ImageCanvasEditorTypes';
|
|
import { ImageCanvasGenerationImageOptionsView } from './ImageCanvasGenerationImageOptionsView';
|
|
import {
|
|
IMAGE_MODEL_GPT_IMAGE_2,
|
|
IMAGE_MODEL_NANOBANANA2,
|
|
} from './ImageCanvasGenerationModel';
|
|
|
|
function ImageOptionsHarness({
|
|
initialDialog,
|
|
onRememberImageModel = vi.fn(),
|
|
}: {
|
|
initialDialog: GenerateDialogState;
|
|
onRememberImageModel?: (model: string) => void;
|
|
}) {
|
|
const [dialog, setDialog] = useState<GenerateDialogState | null>(
|
|
initialDialog,
|
|
);
|
|
|
|
return dialog ? (
|
|
<div>
|
|
<ImageCanvasGenerationImageOptionsView
|
|
dialog={dialog}
|
|
setGenerateDialog={setDialog}
|
|
includeDimensions
|
|
onRememberImageModel={onRememberImageModel}
|
|
cost={12}
|
|
submitLabel="生成"
|
|
submitAriaLabel="生成角色形象"
|
|
/>
|
|
<output aria-label="当前模型">{dialog.imageModel}</output>
|
|
<output aria-label="当前比例">{dialog.aspectRatio}</output>
|
|
<output aria-label="当前尺寸">{dialog.imageSize}</output>
|
|
<output aria-label="当前状态">{dialog.status}</output>
|
|
<output aria-label="当前错误">{dialog.errorMessage ?? '-'}</output>
|
|
</div>
|
|
) : null;
|
|
}
|
|
|
|
describe('ImageCanvasGenerationImageOptionsView', () => {
|
|
it('updates dimensions from a menu, keeps the menu open and marks the selection', () => {
|
|
render(
|
|
<ImageOptionsHarness
|
|
initialDialog={{
|
|
mode: 'character',
|
|
prompt: '',
|
|
status: 'failed',
|
|
errorMessage: '旧错误',
|
|
imageModel: IMAGE_MODEL_NANOBANANA2,
|
|
aspectRatio: '1:1',
|
|
imageSize: '1K',
|
|
}}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(
|
|
screen.getByRole('button', { name: '生成图片尺寸 1:1 · 1K' }),
|
|
);
|
|
const panel = screen.getByRole('menu', { name: '生成图片尺寸选项' });
|
|
|
|
fireEvent.click(within(panel).getByRole('button', { name: '16:9' }));
|
|
fireEvent.click(within(panel).getByRole('button', { name: '2K' }));
|
|
|
|
expect(screen.getByRole('menu', { name: '生成图片尺寸选项' })).toBeTruthy();
|
|
expect(
|
|
within(panel)
|
|
.getByRole('button', { name: '16:9' })
|
|
.getAttribute('aria-pressed'),
|
|
).toBe('true');
|
|
expect(
|
|
within(panel)
|
|
.getByRole('button', { name: '2K' })
|
|
.getAttribute('aria-pressed'),
|
|
).toBe('true');
|
|
expect(
|
|
screen.getByRole('button', { name: '生成图片尺寸 16:9 · 2K' }),
|
|
).toBeTruthy();
|
|
expect(screen.getByLabelText('当前比例').textContent).toBe('16:9');
|
|
expect(screen.getByLabelText('当前尺寸').textContent).toBe('2K');
|
|
expect(screen.getByLabelText('当前状态').textContent).toBe('idle');
|
|
expect(screen.getByLabelText('当前错误').textContent).toBe('-');
|
|
});
|
|
|
|
it('closes option panels when clicking parent panel outside trigger buttons', () => {
|
|
render(
|
|
<ImageOptionsHarness
|
|
initialDialog={{
|
|
mode: 'generate',
|
|
prompt: '',
|
|
status: 'idle',
|
|
imageModel: IMAGE_MODEL_NANOBANANA2,
|
|
aspectRatio: '1:1',
|
|
imageSize: '1K',
|
|
}}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(
|
|
screen.getByRole('button', { name: '生成图片尺寸 1:1 · 1K' }),
|
|
);
|
|
expect(
|
|
screen.getByRole('menu', { name: '生成图片尺寸选项' }),
|
|
).toBeTruthy();
|
|
|
|
fireEvent.click(screen.getByLabelText('当前模型'));
|
|
|
|
expect(
|
|
screen.queryByRole('menu', { name: '生成图片尺寸选项' }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('renders model choices as one model per row and uses a check mark for selection', () => {
|
|
const rememberImageModel = vi.fn();
|
|
render(
|
|
<ImageOptionsHarness
|
|
initialDialog={{
|
|
mode: 'icon',
|
|
prompt: '',
|
|
status: 'idle',
|
|
imageModel: IMAGE_MODEL_NANOBANANA2,
|
|
aspectRatio: '9:16',
|
|
imageSize: '0.5K',
|
|
}}
|
|
onRememberImageModel={rememberImageModel}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(
|
|
screen.getByRole('button', { name: '生成图片模型 nanobanana2' }),
|
|
);
|
|
const panel = screen.getByRole('menu', { name: '生成图片模型选项' });
|
|
|
|
expect(within(panel).getByRole('button', { name: 'nanobanana2' })).toBeTruthy();
|
|
fireEvent.click(within(panel).getByRole('button', { name: 'gpt-image-2' }));
|
|
|
|
expect(screen.getByRole('menu', { name: '生成图片模型选项' })).toBeTruthy();
|
|
expect(rememberImageModel).toHaveBeenCalledWith(IMAGE_MODEL_GPT_IMAGE_2);
|
|
expect(screen.getByLabelText('当前模型').textContent).toBe(
|
|
IMAGE_MODEL_GPT_IMAGE_2,
|
|
);
|
|
expect(screen.getByLabelText('当前比例').textContent).toBe('9:16');
|
|
expect(screen.getByLabelText('当前尺寸').textContent).toBe('1K');
|
|
expect(
|
|
screen.getByRole('button', { name: '生成图片模型 gpt-image-2' }),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it('renders option menus and shows mud point text in the submit button', () => {
|
|
render(
|
|
<ImageOptionsHarness
|
|
initialDialog={{
|
|
mode: 'generate',
|
|
prompt: '',
|
|
status: 'idle',
|
|
imageModel: IMAGE_MODEL_NANOBANANA2,
|
|
aspectRatio: '1:1',
|
|
imageSize: '1K',
|
|
}}
|
|
/>,
|
|
);
|
|
|
|
fireEvent.click(
|
|
screen.getByRole('button', { name: '生成图片尺寸 1:1 · 1K' }),
|
|
);
|
|
|
|
expect(
|
|
screen.getByRole('menu', { name: '生成图片尺寸选项' }),
|
|
).toBeTruthy();
|
|
const submit = screen.getByRole('button', { name: '生成角色形象' });
|
|
expect(submit.textContent).toBe('生成12泥点');
|
|
});
|
|
});
|