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预览和代码高亮,复用按需读取与缓存 移除未调用的活跃回合查询命令,保留正式快照恢复链路 补充回归用例并同步技术方案和团队约定
293 lines
9.0 KiB
TypeScript
293 lines
9.0 KiB
TypeScript
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');
|
||
});
|
||
});
|