14a0286f70
- ResourceCanvasAssetGenerationPanelView:删掉 `phaseDetail` 通道与整套在途状态(`submitting` / 禁用输入 / 「后台运行并关闭」),主按钮文案恒为动作名;提交时**同步**调用 onSubmit 并 onClose, 面板不等 IPC、不等排队、不等生成。新增 `draft` / `error` 两个入参:只有「后端从未受理」的即时 失败重开时,宿主才把草稿与原因带回来(`role="alert"` 可见、改完即可重试) - index.tsx:`submitResourceAssetGeneration` 改为同步返回(不再 await 终局),记录本次提交上下文 (taskId / action / 草稿 / 是否本来就该立刻派发),提交后自动开任务面板并给一次提示条; 收尾回调里区分两类失败:`record === null` 且本来就该立刻派发 → 重开提交面板并带回草稿; 受理之后才失败 → 不重开面板,只在任务面板收口为失败 + 提示条 - index.tsx:删掉只为旧「在途阶段文案」服务的 `resourceAssetGenerationPanelTaskIdRef` 与成功后的 自动收面板逻辑(面板此时已关闭) - 测试:`resourceCanvasAssetGenerationBackgroundClose.test.tsx` 的图片类用例改写为「点击即关闭 + 面板 DOM 里从不出现 排队中。/正在生成。/提交中…」+「即时失败重开带回草稿与原因」;音频面板用例不变 - 测试:`resourceCanvasBottomToolbar.test.tsx` 的失败重试用例按新语义改写(点击即关闭、重开时显示原因) - 测试:AppSurface 新增三条(同步关闭且面板无阶段文案、后端未受理时重开并保留草稿、受理后失败不重开 只在任务面板收口),并把既有生成任务 e2e 的提交步骤改为「点击即关闭」 - 顺带按仓库规范修正以上文件的 eslint import sort 与 prettier 格式(未触碰无关文件)
186 lines
6.4 KiB
TypeScript
186 lines
6.4 KiB
TypeScript
// @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';
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
const uiPrototypeAction: ResourceCanvasAssetToolAction = {
|
|
id: 'generate-ui-prototype',
|
|
route: 'asset',
|
|
label: '生成 UI 设计图',
|
|
assetKind: 'ui-prototype',
|
|
audioKind: null,
|
|
assetName: 'AI 生成 UI 设计图',
|
|
promptPlaceholder: '描述这张界面要承载的玩法与操作',
|
|
adjustableDimensions: true,
|
|
aspectRatio: '16:9',
|
|
imageSize: '1K',
|
|
requiresIconSpecReference: true,
|
|
writesIconSpecReference: false,
|
|
};
|
|
|
|
/** 永不 resolve 的提交:音频面板仍然等生成结束,用它模拟在途状态。 */
|
|
function pendingSubmit() {
|
|
return new Promise<void>(() => undefined);
|
|
}
|
|
|
|
describe('图片类生成面板:点击即关闭,面板里不出现阶段文案', () => {
|
|
test('点击生成同步调用提交并关闭面板,面板 DOM 里从不出现阶段 / 排队文案', async () => {
|
|
const onClose = vi.fn();
|
|
const onSubmit = vi.fn();
|
|
const user = userEvent.setup();
|
|
render(
|
|
<ResourceCanvasAssetGenerationPanelView
|
|
action={uiPrototypeAction}
|
|
onSubmit={onSubmit}
|
|
onClose={onClose}
|
|
/>,
|
|
);
|
|
await user.type(screen.getByLabelText('生成提示词'), '主界面与背包页');
|
|
// 点击前主按钮文案就是动作名:不是阶段、也不是「已提交」。
|
|
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(
|
|
<ResourceCanvasAssetGenerationPanelView
|
|
action={uiPrototypeAction}
|
|
onSubmit={onSubmit}
|
|
onClose={onClose}
|
|
/>,
|
|
);
|
|
await user.type(screen.getByLabelText('生成提示词'), '主界面与背包页');
|
|
|
|
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(
|
|
<ResourceCanvasAssetGenerationPanelView
|
|
action={uiPrototypeAction}
|
|
draft={{
|
|
prompt: '主界面与背包页',
|
|
assetName: 'AI 生成 UI 设计图',
|
|
aspectRatio: '16:9',
|
|
imageSize: '1K',
|
|
}}
|
|
error="生成素材失败:远端拒绝"
|
|
onSubmit={onSubmit}
|
|
onClose={onClose}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByRole('alert').textContent).toContain(
|
|
'生成素材失败:远端拒绝',
|
|
);
|
|
expect(
|
|
(screen.getByLabelText('生成提示词') as HTMLTextAreaElement).value,
|
|
).toBe('主界面与背包页');
|
|
|
|
await user.click(screen.getByRole('button', { name: '生成 UI 设计图' }));
|
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
expect(onSubmit.mock.calls[0]?.[0]).toMatchObject({
|
|
kind: 'ui-prototype',
|
|
prompt: '主界面与背包页',
|
|
assetName: 'AI 生成 UI 设计图',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('音频生成面板在提交期间可关闭', () => {
|
|
test('提交在途时点 × 能关闭面板,且不改动 pending-edit 账本语义', async () => {
|
|
const onClose = vi.fn();
|
|
const onSubmit = vi.fn(pendingSubmit);
|
|
const user = userEvent.setup();
|
|
render(
|
|
<ResourceCanvasGenerationPanelView
|
|
kinds={['sound-effect']}
|
|
initialKind="sound-effect"
|
|
onSubmit={onSubmit}
|
|
onClose={onClose}
|
|
/>,
|
|
);
|
|
await user.type(screen.getByLabelText('生成提示词'), '木门推开的声音');
|
|
await user.click(screen.getByRole('button', { name: '生成音效' }));
|
|
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
|
|
|
|
const closeButton = screen.getByRole('button', {
|
|
name: '关闭生成音效',
|
|
}) as HTMLButtonElement;
|
|
expect(closeButton.disabled).toBe(false);
|
|
await user.click(closeButton);
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
|
|
fireEvent.keyDown(window, { key: 'Escape' });
|
|
expect(onClose).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test('提交在途时提供「后台运行并关闭」', async () => {
|
|
const onClose = vi.fn();
|
|
const user = userEvent.setup();
|
|
render(
|
|
<ResourceCanvasGenerationPanelView
|
|
kinds={['sound-effect']}
|
|
initialKind="sound-effect"
|
|
onSubmit={vi.fn(pendingSubmit)}
|
|
onClose={onClose}
|
|
/>,
|
|
);
|
|
await user.type(screen.getByLabelText('生成提示词'), '木门推开的声音');
|
|
await user.click(screen.getByRole('button', { name: '生成音效' }));
|
|
|
|
await user.click(screen.getByRole('button', { name: '后台运行并关闭' }));
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|