AGC 资源画布新增生成素材浮层与生成入口模型
- 新增 resourceCanvasGenerationModel:按本地契约锁定无源生成放行的视频 / 音效 / 背景音乐三类,提供 create:<operationId> 源标识、入口渲染门禁与请求身份工厂 - 新增 ResourceCanvasGenerationPanelView:独立浮层面板承载类型选择、草稿与失败重试,失败后锁定原请求并复用同一 operationId / idempotencyKey - 浮层只呈现三类真实可用类型,不出现图片专属的模型 / 比例 / 尺寸 / 像素艺术与泥点价 - resourceCanvasChrome.css 补浮层宿主样式 - 新增 resourceCanvasGenerationEntry 定向测试 6 条
This commit is contained in:
+190
@@ -0,0 +1,190 @@
|
|||||||
|
import { Sparkles, X } from 'lucide-react';
|
||||||
|
import { type FormEvent, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { PlatformActionButton } from '../../../../../packages/shared/src/components/PlatformActionButton';
|
||||||
|
import { PlatformSegmentedTabs } from '../../../../../packages/shared/src/components/PlatformSegmentedTabs';
|
||||||
|
import { PlatformTextField } from '../../../../../packages/shared/src/components/PlatformTextField';
|
||||||
|
import { ThemedModal } from '../../components/modal/ThemedModal';
|
||||||
|
import { resourceEditPromptMaxLength } from '../../view/project-development/resourceEditModel';
|
||||||
|
import {
|
||||||
|
createResourceCanvasGenerationRequest,
|
||||||
|
RESOURCE_CANVAS_GENERATION_DEFAULT_KIND,
|
||||||
|
RESOURCE_CANVAS_GENERATION_OPTIONS,
|
||||||
|
type ResourceCanvasGenerationKind,
|
||||||
|
resourceCanvasGenerationOption,
|
||||||
|
} from './resourceCanvasGenerationModel';
|
||||||
|
|
||||||
|
export type ResourceCanvasGenerationSubmitInput = {
|
||||||
|
kind: ResourceCanvasGenerationKind;
|
||||||
|
operationId: string;
|
||||||
|
idempotencyKey: string;
|
||||||
|
prompt: string;
|
||||||
|
assetName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResourceCanvasGenerationPanelViewProps = {
|
||||||
|
initialKind?: ResourceCanvasGenerationKind;
|
||||||
|
onSubmit: (input: ResourceCanvasGenerationSubmitInput) => Promise<void>;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const RESOURCE_GENERATION_KIND_ITEMS = RESOURCE_CANVAS_GENERATION_OPTIONS.map(
|
||||||
|
(option) => ({ id: option.kind, label: option.label }),
|
||||||
|
);
|
||||||
|
|
||||||
|
function resourceGenerationErrorMessage(error: unknown) {
|
||||||
|
if (typeof error === 'string' && error.trim()) return error;
|
||||||
|
if (error instanceof Error && error.message) return error.message;
|
||||||
|
return '生成素材失败';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源画布「生成入口」的浮层面板。
|
||||||
|
*
|
||||||
|
* 形态是独立弹层(`ThemedModal`,与资源面板 / 分类面板同一套宿主 chrome),
|
||||||
|
* 不在任何现有面板下面追加内容;提交链路与结果定位由宿主 `index.tsx` 负责,
|
||||||
|
* 面板只持有草稿、类型选择与失败重试状态。
|
||||||
|
*/
|
||||||
|
export function ResourceCanvasGenerationPanelView({
|
||||||
|
initialKind = RESOURCE_CANVAS_GENERATION_DEFAULT_KIND,
|
||||||
|
onSubmit,
|
||||||
|
onClose,
|
||||||
|
}: ResourceCanvasGenerationPanelViewProps) {
|
||||||
|
const [kind, setKind] = useState<ResourceCanvasGenerationKind>(initialKind);
|
||||||
|
const option = resourceCanvasGenerationOption(kind);
|
||||||
|
const [prompt, setPrompt] = useState('');
|
||||||
|
const [assetName, setAssetName] = useState(option.assetName);
|
||||||
|
const [attempted, setAttempted] = useState(false);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
// 同一次生成会话只申请一份请求身份:失败重试必须命中同一 operation 账本。
|
||||||
|
const requestRef = useRef<{
|
||||||
|
operationId: string;
|
||||||
|
idempotencyKey: string;
|
||||||
|
} | null>(null);
|
||||||
|
const inputLocked = attempted || submitting;
|
||||||
|
|
||||||
|
async function submit(event: FormEvent<HTMLFormElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
const normalizedPrompt = prompt.trim();
|
||||||
|
const normalizedAssetName = assetName.trim();
|
||||||
|
if (!normalizedPrompt || !normalizedAssetName || submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAttempted(true);
|
||||||
|
setSubmitting(true);
|
||||||
|
setError(null);
|
||||||
|
requestRef.current ??= createResourceCanvasGenerationRequest();
|
||||||
|
try {
|
||||||
|
await onSubmit({
|
||||||
|
kind,
|
||||||
|
operationId: requestRef.current.operationId,
|
||||||
|
idempotencyKey: requestRef.current.idempotencyKey,
|
||||||
|
prompt: normalizedPrompt,
|
||||||
|
assetName: normalizedAssetName,
|
||||||
|
});
|
||||||
|
} catch (submitError) {
|
||||||
|
setError(resourceGenerationErrorMessage(submitError));
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemedModal
|
||||||
|
open
|
||||||
|
ariaLabel="生成素材"
|
||||||
|
closeOnBackdrop={!submitting}
|
||||||
|
closeOnEscape={!submitting}
|
||||||
|
onClose={() => {
|
||||||
|
if (!submitting) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
panelClassName="game-approval-dialog game-resource-generation-dialog"
|
||||||
|
>
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<h2>生成素材</h2>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="关闭生成素材"
|
||||||
|
disabled={submitting}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<X size={16} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<PlatformSegmentedTabs
|
||||||
|
items={RESOURCE_GENERATION_KIND_ITEMS}
|
||||||
|
activeId={kind}
|
||||||
|
ariaLabel="生成素材类型"
|
||||||
|
columns="three"
|
||||||
|
gap="sm"
|
||||||
|
size="compact"
|
||||||
|
disabled={inputLocked}
|
||||||
|
onChange={(nextKind) => {
|
||||||
|
setKind(nextKind);
|
||||||
|
setAssetName(resourceCanvasGenerationOption(nextKind).assetName);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<form className="game-resource-generation-form" onSubmit={submit}>
|
||||||
|
<label>
|
||||||
|
<span>素材名称</span>
|
||||||
|
<PlatformTextField
|
||||||
|
aria-label="素材名称"
|
||||||
|
maxLength={120}
|
||||||
|
disabled={inputLocked}
|
||||||
|
value={assetName}
|
||||||
|
onChange={(event) => setAssetName(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>生成提示词</span>
|
||||||
|
<PlatformTextField
|
||||||
|
variant="textarea"
|
||||||
|
aria-label="生成提示词"
|
||||||
|
rows={6}
|
||||||
|
autoFocus
|
||||||
|
disabled={inputLocked}
|
||||||
|
maxLength={resourceEditPromptMaxLength(option.editKind)}
|
||||||
|
placeholder={option.promptPlaceholder}
|
||||||
|
value={prompt}
|
||||||
|
onChange={(event) => setPrompt(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{error ? (
|
||||||
|
<p className="game-resource-generation-error" role="alert">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
<div className="game-resource-generation-actions">
|
||||||
|
<PlatformActionButton
|
||||||
|
type="button"
|
||||||
|
tone="secondary"
|
||||||
|
disabled={submitting}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</PlatformActionButton>
|
||||||
|
{error ? (
|
||||||
|
<PlatformActionButton type="submit" disabled={submitting}>
|
||||||
|
<Sparkles size={15} aria-hidden="true" />
|
||||||
|
使用原请求重试
|
||||||
|
</PlatformActionButton>
|
||||||
|
) : (
|
||||||
|
<PlatformActionButton
|
||||||
|
type="submit"
|
||||||
|
disabled={
|
||||||
|
submitting || !prompt.trim() || !assetName.trim() || inputLocked
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Sparkles size={15} aria-hidden="true" />
|
||||||
|
{submitting ? '生成中…' : option.generationLabel}
|
||||||
|
</PlatformActionButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ThemedModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -373,4 +373,37 @@
|
|||||||
.game-resource-panel-meta small {
|
.game-resource-panel-meta small {
|
||||||
color: #8c6252;
|
color: #8c6252;
|
||||||
font-size: 0.68rem;
|
font-size: 0.68rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 生成素材浮层:独立弹层承载类型选择、草稿与失败重试。 */
|
||||||
|
.game-resource-generation-dialog {
|
||||||
|
width: min(560px, 100%);
|
||||||
|
max-height: min(720px, calc(100dvh - 40px));
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-resource-generation-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-resource-generation-form label {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
color: #76594e;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-resource-generation-error {
|
||||||
|
margin: 0;
|
||||||
|
color: #b3261e;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-resource-generation-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
import type { LocalProjectResourceEditKind } from '../../view/project-development/resourceEditModel';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源画布「生成入口」当前能真正落地的三类新建素材。
|
||||||
|
*
|
||||||
|
* 这里的白名单与 Rust `resolve_resource_edit_source` 的 create 分支一一对应:
|
||||||
|
* `apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs` 只放行
|
||||||
|
* video / sound-effect / background-music,其余类型(含图片)会直接返回
|
||||||
|
* “当前资源类型不支持无源生成”。因此入口只呈现这三类——宁可少一个入口,
|
||||||
|
* 也不给一个提交必然失败、或者拿图片参数糊弄用户的按钮。
|
||||||
|
*/
|
||||||
|
export type ResourceCanvasGenerationKind =
|
||||||
|
| 'video'
|
||||||
|
| 'sound-effect'
|
||||||
|
| 'background-music';
|
||||||
|
|
||||||
|
export type ResourceCanvasGenerationOption = {
|
||||||
|
kind: ResourceCanvasGenerationKind;
|
||||||
|
editKind: LocalProjectResourceEditKind;
|
||||||
|
/** 浮层内类型选择器的短标签。 */
|
||||||
|
label: string;
|
||||||
|
/** 入口按钮与提交按钮的完整文案。 */
|
||||||
|
generationLabel: string;
|
||||||
|
sourceMediaType: string;
|
||||||
|
/** 新建素材的默认名称,与旧 `resourceGenerationOptions` 口径一致。 */
|
||||||
|
assetName: string;
|
||||||
|
promptPlaceholder: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VIDEO_GENERATION_OPTION: ResourceCanvasGenerationOption = {
|
||||||
|
kind: 'video',
|
||||||
|
editKind: 'video',
|
||||||
|
label: '视频',
|
||||||
|
generationLabel: '生成视频',
|
||||||
|
sourceMediaType: 'video/mp4',
|
||||||
|
assetName: '新视频',
|
||||||
|
promptPlaceholder: '描述想生成的视频画面、镜头与节奏',
|
||||||
|
};
|
||||||
|
|
||||||
|
const SOUND_EFFECT_GENERATION_OPTION: ResourceCanvasGenerationOption = {
|
||||||
|
kind: 'sound-effect',
|
||||||
|
editKind: 'sound-effect',
|
||||||
|
label: '音效',
|
||||||
|
generationLabel: '生成音效',
|
||||||
|
sourceMediaType: 'audio/mpeg',
|
||||||
|
assetName: '新音效',
|
||||||
|
promptPlaceholder: '描述想生成的音效,例如:木门缓慢推开时的吱呀声',
|
||||||
|
};
|
||||||
|
|
||||||
|
const BACKGROUND_MUSIC_GENERATION_OPTION: ResourceCanvasGenerationOption = {
|
||||||
|
kind: 'background-music',
|
||||||
|
editKind: 'background-music',
|
||||||
|
label: '背景音乐',
|
||||||
|
generationLabel: '生成背景音乐',
|
||||||
|
sourceMediaType: 'audio/mpeg',
|
||||||
|
assetName: '新背景音乐',
|
||||||
|
promptPlaceholder: '描述想生成的背景音乐风格、情绪与乐器',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_CANVAS_GENERATION_OPTIONS: readonly ResourceCanvasGenerationOption[] =
|
||||||
|
[
|
||||||
|
VIDEO_GENERATION_OPTION,
|
||||||
|
SOUND_EFFECT_GENERATION_OPTION,
|
||||||
|
BACKGROUND_MUSIC_GENERATION_OPTION,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const RESOURCE_CANVAS_GENERATION_DEFAULT_KIND: ResourceCanvasGenerationKind =
|
||||||
|
'video';
|
||||||
|
|
||||||
|
export function resourceCanvasGenerationOption(
|
||||||
|
kind: ResourceCanvasGenerationKind,
|
||||||
|
): ResourceCanvasGenerationOption {
|
||||||
|
if (kind === 'sound-effect') {
|
||||||
|
return SOUND_EFFECT_GENERATION_OPTION;
|
||||||
|
}
|
||||||
|
if (kind === 'background-music') {
|
||||||
|
return BACKGROUND_MUSIC_GENERATION_OPTION;
|
||||||
|
}
|
||||||
|
return VIDEO_GENERATION_OPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create 模式的稳定源标识。
|
||||||
|
*
|
||||||
|
* 无源生成没有真实源资源,Rust 只把这个字符串当作 canonical resource id 记账,
|
||||||
|
* 不再回读源文件;用 operationId 拼出来保证同一 operation 的流水可对账。
|
||||||
|
*/
|
||||||
|
export function resourceCanvasGenerationSourceId(operationId: string) {
|
||||||
|
return `create:${operationId.trim()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 入口按钮的渲染门禁。
|
||||||
|
*
|
||||||
|
* 没有客户端 invoke 桥(浏览器里打开的构建)或项目还没就绪时,生成能力不成立,
|
||||||
|
* 入口整体不渲染,避免留下点了没反应的按钮。
|
||||||
|
*/
|
||||||
|
export function isResourceCanvasGenerationAvailable({
|
||||||
|
hasRuntimeInvoke,
|
||||||
|
projectPath,
|
||||||
|
projectId,
|
||||||
|
}: {
|
||||||
|
hasRuntimeInvoke: boolean;
|
||||||
|
projectPath: string;
|
||||||
|
projectId: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
hasRuntimeInvoke &&
|
||||||
|
projectPath.trim().length > 0 &&
|
||||||
|
projectId.trim().length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一次生成会话的请求身份。
|
||||||
|
*
|
||||||
|
* 面板打开时生成一次,失败重试继续复用同一对 operationId / idempotencyKey:
|
||||||
|
* 命中同一 operation 账本,既不会重复扣费,也不会在服务端另起一次生成。
|
||||||
|
*/
|
||||||
|
export function createResourceCanvasGenerationRequest() {
|
||||||
|
return {
|
||||||
|
operationId: crypto.randomUUID(),
|
||||||
|
idempotencyKey: crypto.randomUUID(),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import {
|
||||||
|
cleanup,
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
waitFor,
|
||||||
|
within,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
isResourceCanvasGenerationAvailable,
|
||||||
|
RESOURCE_CANVAS_GENERATION_OPTIONS,
|
||||||
|
resourceCanvasGenerationSourceId,
|
||||||
|
} from '../src/features/resource-canvas/resourceCanvasGenerationModel';
|
||||||
|
import { ResourceCanvasGenerationPanelView } from '../src/features/resource-canvas/ResourceCanvasGenerationPanelView';
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resourceCanvasGenerationModel', () => {
|
||||||
|
test('只放行本地契约支持无源生成的三类素材', () => {
|
||||||
|
expect(RESOURCE_CANVAS_GENERATION_OPTIONS.map((item) => item.kind)).toEqual(
|
||||||
|
['video', 'sound-effect', 'background-music'],
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
RESOURCE_CANVAS_GENERATION_OPTIONS.map((item) => item.editKind),
|
||||||
|
).toEqual(['video', 'sound-effect', 'background-music']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('没有客户端桥或项目未就绪时生成能力不成立', () => {
|
||||||
|
expect(
|
||||||
|
isResourceCanvasGenerationAvailable({
|
||||||
|
hasRuntimeInvoke: false,
|
||||||
|
projectPath: '/tmp/project',
|
||||||
|
projectId: 'project-1',
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
isResourceCanvasGenerationAvailable({
|
||||||
|
hasRuntimeInvoke: true,
|
||||||
|
projectPath: ' ',
|
||||||
|
projectId: 'project-1',
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
isResourceCanvasGenerationAvailable({
|
||||||
|
hasRuntimeInvoke: true,
|
||||||
|
projectPath: '/tmp/project',
|
||||||
|
projectId: '',
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
isResourceCanvasGenerationAvailable({
|
||||||
|
hasRuntimeInvoke: true,
|
||||||
|
projectPath: '/tmp/project',
|
||||||
|
projectId: 'project-1',
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('无源生成的稳定源标识挂在 operationId 上', () => {
|
||||||
|
expect(resourceCanvasGenerationSourceId(' op-1 ')).toBe('create:op-1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ResourceCanvasGenerationPanelView', () => {
|
||||||
|
test('只呈现三类可无源生成的素材,也不展示图片专属选项或泥点价', () => {
|
||||||
|
render(
|
||||||
|
<ResourceCanvasGenerationPanelView
|
||||||
|
onSubmit={async () => undefined}
|
||||||
|
onClose={() => undefined}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const panel = screen.getByRole('dialog', { name: '生成素材' });
|
||||||
|
for (const label of ['视频', '音效', '背景音乐']) {
|
||||||
|
expect(within(panel).getByRole('button', { name: label })).not.toBeNull();
|
||||||
|
}
|
||||||
|
expect(panel.textContent).not.toContain('泥点');
|
||||||
|
expect(panel.textContent).not.toContain('像素艺术');
|
||||||
|
expect(panel.textContent).not.toContain('图片');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('失败后锁定原请求并用同一 operationId 与幂等键重试', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onSubmit = vi
|
||||||
|
.fn<(input: unknown) => Promise<void>>()
|
||||||
|
.mockRejectedValueOnce(new Error('result-unknown: 测试网络中断'))
|
||||||
|
.mockResolvedValueOnce(undefined);
|
||||||
|
render(
|
||||||
|
<ResourceCanvasGenerationPanelView
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
onClose={() => undefined}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const prompt = screen.getByLabelText('生成提示词');
|
||||||
|
const assetName = screen.getByLabelText('素材名称');
|
||||||
|
expect((assetName as HTMLInputElement).value).toBe('新视频');
|
||||||
|
await user.type(prompt, '一段片头动画,镜头缓慢推进');
|
||||||
|
await user.click(screen.getByRole('button', { name: '生成视频' }));
|
||||||
|
|
||||||
|
expect((await screen.findByRole('alert')).textContent).toContain(
|
||||||
|
'result-unknown: 测试网络中断',
|
||||||
|
);
|
||||||
|
expect((prompt as HTMLTextAreaElement).disabled).toBe(true);
|
||||||
|
expect((assetName as HTMLInputElement).disabled).toBe(true);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: '使用原请求重试' }));
|
||||||
|
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(2));
|
||||||
|
expect(onSubmit.mock.calls[0]?.[0]).toEqual(onSubmit.mock.calls[1]?.[0]);
|
||||||
|
expect(onSubmit.mock.calls[0]?.[0]).toMatchObject({
|
||||||
|
kind: 'video',
|
||||||
|
prompt: '一段片头动画,镜头缓慢推进',
|
||||||
|
assetName: '新视频',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('切换类型后默认名称与提交文案跟随类型', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<ResourceCanvasGenerationPanelView
|
||||||
|
onSubmit={async () => undefined}
|
||||||
|
onClose={() => undefined}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: '背景音乐' }));
|
||||||
|
expect((screen.getByLabelText('素材名称') as HTMLInputElement).value).toBe(
|
||||||
|
'新背景音乐',
|
||||||
|
);
|
||||||
|
expect(screen.getByRole('button', { name: '生成背景音乐' })).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user