diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasToolbarModel.test.ts b/apps/ai-game-creator-shell/tests/resourceCanvasToolbarModel.test.ts new file mode 100644 index 000000000..abc5efa09 --- /dev/null +++ b/apps/ai-game-creator-shell/tests/resourceCanvasToolbarModel.test.ts @@ -0,0 +1,163 @@ +import { describe, expect, it } from 'vitest'; + +import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp'; +import { resolveResourceCanvasToolbarActions } from '../src/features/resource-canvas/resourceCanvasToolbarModel'; +import type { ProjectResource } from '../src/view/project-development/resourceProjectionModel'; + +function createResource( + overrides: Partial = {}, +): ProjectResource { + return { + id: 'asset:hero', + category: 'art', + subtype: 'icon', + label: '主角立绘', + path: 'assets/hero.png', + mediaType: 'image/png', + sourceLabel: '生成', + taskTitle: null, + manifestAssetId: 'hero', + producerTaskId: null, + externalResourceId: null, + referenceResourceIds: [], + dependencies: [], + dependencyDepth: 0, + ...overrides, + }; +} + +function createManifest() { + return createGameCreationAppManifest( + 'project-toolbar-actions', + '工具条动作测试', + ); +} + +/** 把种子任务的第一个任务改成已完成并登记指定产物,用于覆盖任务产物正规化门禁。 */ +function createManifestWithProducingTask( + status: 'completed' | 'running', + artifactPath: string, +) { + const manifest = createManifest(); + manifest.tasks = manifest.tasks.map((task, index) => + index === 0 ? { ...task, status, artifacts: [artifactPath] } : task, + ); + return manifest; +} + +function toolbarActions( + manifest: ReturnType, + resource: ProjectResource, +) { + return [...resolveResourceCanvasToolbarActions(manifest, resource)]; +} + +/** + * AGC 资源画布工具条的放行判据只有一条:**进集合的动作必须真能跑通**。 + * + * `ImageCanvasSelectedLayerToolbarView` 收到 `supportedActions` 后完全以集合为准, + * 宿主编排层里还是空回调的动作一旦进集合就会渲染成"点了没反应"的按钮,因此这里 + * 逐条钉住当前真正接通的动作集合(见 + * `docs/project-memory/shared-memory/pitfalls.md` 的「资源画布工具条的『改造』 + * 『角色动画』按钮点了没反应」)。 + */ +describe('resource canvas toolbar actions', () => { + it('已登记 manifest 资产的栅格图片只放行真实接通的快速编辑', () => { + expect(toolbarActions(createManifest(), createResource())).toEqual([ + 'quick-edit', + ]); + }); + + it('音频与视频资源不再放行宿主编排层空回调的改造动作', () => { + const manifest = createManifest(); + expect( + toolbarActions( + manifest, + createResource({ + id: 'asset:bgm', + category: 'audio', + subtype: 'background-music', + label: '背景音乐', + path: 'assets/bgm.mp3', + mediaType: 'audio/mpeg', + manifestAssetId: 'bgm', + }), + ), + ).toEqual([]); + expect( + toolbarActions( + manifest, + createResource({ + id: 'asset:intro', + subtype: 'video', + label: '开场动画', + path: 'assets/intro.mp4', + mediaType: 'video/mp4', + manifestAssetId: 'intro', + }), + ), + ).toEqual([]); + }); + + it('角色资源只放行快速编辑,不再放行空回调的角色动画动作', () => { + expect( + toolbarActions( + createManifest(), + createResource({ id: 'asset:hero-action', subtype: 'character' }), + ), + ).toEqual(['quick-edit']); + }); + + it('未登记 manifest 资产且没有已完成任务产物背书的资源不放行任何动作', () => { + expect( + toolbarActions( + createManifest(), + createResource({ manifestAssetId: null, producerTaskId: 'task-art' }), + ), + ).toEqual([]); + }); + + it('只认已完成任务的栅格产物,未完成任务的产物不放行快速编辑', () => { + const artifactPath = 'assets/hero.png'; + const completedManifest = createManifestWithProducingTask( + 'completed', + artifactPath, + ); + expect( + toolbarActions( + completedManifest, + createResource({ + manifestAssetId: null, + producerTaskId: completedManifest.tasks[0].id, + }), + ), + ).toEqual(['quick-edit']); + + const runningManifest = createManifestWithProducingTask( + 'running', + artifactPath, + ); + expect( + toolbarActions( + runningManifest, + createResource({ + manifestAssetId: null, + producerTaskId: runningManifest.tasks[0].id, + }), + ), + ).toEqual([]); + }); + + it('非栅格图片不进快速编辑的派生门禁', () => { + expect( + toolbarActions( + createManifest(), + createResource({ + id: 'asset:icon', + path: 'assets/icon.svg', + mediaType: 'image/svg+xml', + }), + ), + ).toEqual([]); + }); +});