// @vitest-environment jsdom
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, describe, expect, test, vi } from 'vitest';
import { ResourceCanvasAssetGenerationPanelView } from '../src/features/resource-canvas/ResourceCanvasAssetGenerationPanelView';
import type { ResourceCanvasAssetToolAction } from '../src/features/resource-canvas/resourceCanvasBottomToolbarModel';
import { ResourceCanvasGenerationPanelView } from '../src/features/resource-canvas/ResourceCanvasGenerationPanelView';
import {
generationPromptText,
typeGenerationPrompt,
} from './resourceGenerationPromptTestUtils';
afterEach(() => {
cleanup();
});
const uiPrototypeAction: ResourceCanvasAssetToolAction = {
id: 'generate-ui-prototype',
route: 'asset',
label: '生成 UI 设计图',
assetKind: 'ui-design',
audioKind: null,
assetName: 'AI 生成 UI 设计图',
promptPlaceholder: '描述这张界面要承载的玩法与操作',
adjustableDimensions: true,
aspectRatio: '16:9',
imageSize: '1K',
requiresIconSpecReference: true,
writesIconSpecReference: false,
};
describe('图片类生成面板:点击即关闭,面板里不出现阶段文案', () => {
test('点击生成同步调用提交并关闭面板,面板 DOM 里从不出现阶段 / 排队文案', async () => {
const onClose = vi.fn();
const onSubmit = vi.fn();
const user = userEvent.setup();
render(
,
);
await typeGenerationPrompt(document.body, '主界面与背包页');
// 点击前主按钮文案就是动作名:不是阶段、也不是「已提交」。
const panel = screen.getByRole('dialog', { name: '生成 UI 设计图' });
expect(
(
within(panel).getByRole('button', {
name: '生成 UI 设计图',
}) as HTMLButtonElement
).disabled,
).toBe(false);
expect(panel.textContent).not.toMatch(/排队中。|正在生成。|提交中…/);
await user.click(
within(panel).getByRole('button', { name: '生成 UI 设计图' }),
);
// 提交与关闭在同一个事件循环里发生:面板不等受理、不等排队、不等生成。
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
expect(panel.textContent).not.toMatch(/排队中。|正在生成。|提交中…/);
expect(screen.queryByRole('button', { name: '后台运行并关闭' })).toBeNull();
});
test('× / Esc / 点遮罩仍能关面板,且关闭不等于取消', async () => {
const onClose = vi.fn();
const onSubmit = vi.fn();
const user = userEvent.setup();
render(
,
);
await typeGenerationPrompt(document.body, '主界面与背包页');
await user.click(
screen.getByRole('button', { name: '关闭生成 UI 设计图' }),
);
expect(onClose).toHaveBeenCalledTimes(1);
fireEvent.keyDown(window, { key: 'Escape' });
expect(onClose).toHaveBeenCalledTimes(2);
const backdrop = document.querySelector('.fixed.inset-0') as HTMLElement;
fireEvent.pointerDown(backdrop);
fireEvent.pointerUp(backdrop);
fireEvent.click(backdrop);
expect(onClose).toHaveBeenCalledTimes(3);
expect(onSubmit).not.toHaveBeenCalled();
});
test('即时失败重开时带回草稿与失败原因,改完就能重试', async () => {
const onClose = vi.fn();
const onSubmit = vi.fn();
const user = userEvent.setup();
render(
,
);
expect(screen.getByRole('alert').textContent).toContain(
'生成素材失败:远端拒绝',
);
await waitFor(() =>
expect(generationPromptText(document.body)).toBe('主界面与背包页'),
);
await user.click(screen.getByRole('button', { name: '生成 UI 设计图' }));
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit.mock.calls[0]?.[0]).toMatchObject({
kind: 'ui-design',
content: [{ type: 'input_text', text: '主界面与背包页' }],
assetName: 'AI 生成 UI 设计图',
});
});
});
/* 音频面板没有在途状态:点「生成」即把输入交给宿主(同步入参 + 立即关闭),阶段文案只由
画布上的「生成任务」侧栏拥有。 */
describe('音频生成面板:点击即关闭,面板里不出现阶段文案', () => {
test('点「生成音效」同步调用提交并关闭面板,从不出现阶段 / 后台文案', async () => {
const onClose = vi.fn();
const onSubmit = vi.fn();
const user = userEvent.setup();
render(
,
);
await user.type(screen.getByLabelText('生成提示词'), '木门推开的声音');
const panel = screen.getByRole('dialog', { name: '生成音效' });
expect(panel.textContent).not.toMatch(/排队中。|正在生成。|提交中…/);
await user.click(within(panel).getByRole('button', { name: '生成音效' }));
// 提交与关闭在同一个事件循环里发生:不等受理、不等排队、不等生成。
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit.mock.calls[0]?.[0]).toMatchObject({
kind: 'sound-effect',
prompt: '木门推开的声音',
assetName: '新音效',
});
expect(onClose).toHaveBeenCalledTimes(1);
// 进度只出现在「生成任务」侧栏:面板 DOM 里既没有阶段文案,也没有在途按钮。
expect(panel.textContent).not.toMatch(/排队中。|正在生成。|提交中…/);
expect(screen.queryByRole('button', { name: '后台运行并关闭' })).toBeNull();
});
test('× / Esc 关面板只收起草稿:不提交,也不取消任何任务', async () => {
const onClose = vi.fn();
const onSubmit = vi.fn();
const user = userEvent.setup();
render(
,
);
await user.type(screen.getByLabelText('生成提示词'), '木门推开的声音');
await user.click(screen.getByRole('button', { name: '关闭生成音效' }));
expect(onClose).toHaveBeenCalledTimes(1);
// 关闭把当前草稿交回宿主:用户再点开占位卡能接着编辑,而不是回到空表单。
expect(onClose.mock.calls[0]?.[0]).toMatchObject({
kind: 'sound-effect',
prompt: '木门推开的声音',
assetName: '新音效',
});
fireEvent.keyDown(window, { key: 'Escape' });
expect(onClose).toHaveBeenCalledTimes(2);
expect(onSubmit).not.toHaveBeenCalled();
});
});