Files
Genarrative/apps/ai-game-creator-shell/tests/resourceCanvasToolbarModel.test.ts
suzmii 5cd02aa8b0
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m44s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m45s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m46s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m49s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m53s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m18s
Project CI / Frontend tests (pull_request) Failing after 4m6s
Project CI / Native shell tests (pull_request) Successful in 6m12s
Project CI / Repository checks (pull_request) Failing after 3m48s
Project CI / Backend tests (pull_request) Successful in 7m8s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m59s
统一智能体对话层级并完善素材导出与文档预览
当前Agent与策划Agent共用正文和过程表现组件,统一思考及工具调用的小字号浅色样式
素材工具栏按实际动作分组,导出紧邻删除并修复空分组重复分隔线
文档与代码使用共享Markdown预览和代码高亮,复用按需读取与缓存
移除未调用的活跃回合查询命令,保留正式快照恢复链路
补充回归用例并同步技术方案和团队约定
2026-09-16 03:59:27 +08:00

293 lines
9.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
ProjectVersionResourceSummary,
} from '../src/view/project-development/resourceProjectionModel';
function createResource(
overrides: Partial<ProjectResource> = {},
): ProjectResource {
return {
id: 'asset:hero',
category: 'ui-interaction',
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 createVersionSummary(): ProjectVersionResourceSummary {
return {
versionId: 'version-1',
parentVersionId: null,
projectRevision: 3,
resourceBindings: [],
createdReason: 'initial',
createdAt: 1,
label: '版本 1',
childVersionIds: [],
};
}
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<typeof createManifest>,
resource: ProjectResource,
) {
return [...resolveResourceCanvasToolbarActions(manifest, resource)];
}
/**
* AGC 资源画布工具条的放行判据只有一条:**进集合的动作必须真能跑通**。
*
* `ImageCanvasSelectedLayerToolbarView` 收到 `supportedActions` 后完全以集合为准,
* 宿主编排层里还是空回调的动作一旦进集合就会渲染成"点了没反应"的按钮,因此这里
* 逐条钉住当前真正接通的动作集合(见
* `docs/project-memory/shared-memory/pitfalls.md` 的「资源画布工具条的『改造』
* 『角色动画』按钮点了没反应」)。
*
* 当前真实接通的是「快速编辑」(normalize → derive(editKind='image-reference'))、
* 「生成动画」(normalize → derive(editKind='character-animation') → 服务端
* `/api/editor/character-animations/generations`)与「下载」(资源面板同一条原生保存
* 对话框 + `save_local_project_asset_file` 链路)。
*/
describe('resource canvas toolbar actions', () => {
it('已登记 manifest 资产的栅格图片放行快速编辑与下载', () => {
expect(toolbarActions(createManifest(), createResource())).toEqual([
'quick-edit',
'download',
]);
});
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(['download']);
expect(
toolbarActions(
manifest,
createResource({
id: 'asset:intro',
subtype: 'video',
label: '开场动画',
path: 'assets/intro.mp4',
mediaType: 'video/mp4',
manifestAssetId: 'intro',
}),
),
).toEqual(['download']);
});
it('角色资源放行「生成动画」:源是已登记的栅格图片', () => {
// 这条断言由「不再放行空回调的角色动画动作」改写而来(四不写:旧断言描述的是
// 未接通状态,已被真实链路取代,不留墓碑)。宿主编排层现在把
// `onOpenCharacterAnimationPanel` 接到 `submitResourceCharacterAnimation`
// derive editKind='character-animation'),所以进集合不再是假按钮。
expect(
toolbarActions(
createManifest(),
createResource({ id: 'asset:hero-action', subtype: 'character' }),
),
).toEqual(['quick-edit', 'character-animation', 'download']);
});
it('非角色的图片资源不放行生成动画,避免渲染出与源类型不匹配的按钮', () => {
expect(
toolbarActions(
createManifest(),
createResource({ id: 'asset:scene', subtype: 'scene' }),
),
).toEqual(['quick-edit', 'download']);
expect(
toolbarActions(
createManifest(),
createResource({ id: 'asset:icon', subtype: 'icon' }),
),
).toEqual(['quick-edit', 'download']);
});
it('角色资源没登记成正式素材时不放行生成动画,只留下真能跑通的下载', () => {
expect(
toolbarActions(
createManifest(),
createResource({
id: 'asset:hero-artifact',
subtype: 'character',
manifestAssetId: null,
producerTaskId: 'task-hero',
}),
),
).toEqual(['download']);
});
it('非栅格的角色资源不放行生成动画:Rust 要解码源图取宽高,SVG 会直接失败', () => {
expect(
toolbarActions(
createManifest(),
createResource({
id: 'asset:hero-svg',
subtype: 'character',
path: 'assets/hero.svg',
mediaType: 'image/svg+xml',
}),
),
).toEqual(['download']);
});
it('未登记 manifest 资产且没有已完成任务产物背书的资源只放行下载', () => {
expect(
toolbarActions(
createManifest(),
createResource({ manifestAssetId: null, producerTaskId: 'task-art' }),
),
).toEqual(['download']);
});
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', 'download']);
const runningManifest = createManifestWithProducingTask(
'running',
artifactPath,
);
expect(
toolbarActions(
runningManifest,
createResource({
manifestAssetId: null,
producerTaskId: runningManifest.tasks[0].id,
}),
),
).toEqual(['download']);
});
it('非栅格图片不进快速编辑的派生门禁,但仍可下载', () => {
expect(
toolbarActions(
createManifest(),
createResource({
id: 'asset:icon',
path: 'assets/icon.svg',
mediaType: 'image/svg+xml',
}),
),
).toEqual(['download']);
});
it('拿得到本地文件路径的资源才放行下载', () => {
expect(toolbarActions(createManifest(), createResource())).toContain(
'download',
);
});
it.each([
['image/png', 'character', 'assets/hero.png'],
['image/svg+xml', 'icon', 'assets/icon.svg'],
['image/png', 'character-animation', 'assets/walk.png'],
['video/mp4', 'video', 'assets/intro.mp4'],
['audio/wav', 'sound-effect', 'assets/hit.wav'],
['audio/mpeg', 'background-music', 'assets/music.mp3'],
['text/html', 'ui-prototype', 'ui/menu.html'],
['text/markdown', 'document', 'docs/design.md'],
['application/json', 'other', 'assets/data.json'],
['application/octet-stream', 'other', 'assets/model.bin'],
])(
'%s 素材导出不依赖媒体类型、预览或 manifest 身份',
(mediaType, subtype, path) => {
expect(
toolbarActions(
createManifest(),
createResource({
mediaType,
subtype,
path,
manifestAssetId: null,
}),
),
).toContain('download');
},
);
it('拿不到本地文件路径的资源不放行下载,避免渲染出点了报错的按钮', () => {
const manifest = createManifest();
expect(
toolbarActions(
manifest,
createResource({ path: '', manifestAssetId: 'virtual' }),
),
).not.toContain('download');
expect(
toolbarActions(
manifest,
createResource({
id: 'version:1',
category: 'version',
subtype: 'version',
label: '版本 1',
path: '',
mediaType: 'application/vnd.genarrative.project-version+json',
manifestAssetId: null,
version: createVersionSummary(),
}),
),
).not.toContain('download');
});
});