Files
Genarrative/apps/ai-game-creator-shell/tests/resourceCanvasToolbarModel.test.ts
T
suzmii 2f8753f597 AGC 资源画布切换为 6 类资产分类加项目版本栏目(阶段二:投影轴与栏目表落地)
- 契约层把 ProjectResourceCanvasPosition.section 加宽为 PersistedProjectResourceCanvasSection 并删除旧 ProjectResourceCanvasSection 联合
- 七个分区类型消费点改用 ProjectResourceCanvasCategory:投影、布局模型、布局 Hook、分区高度、依赖图层与画布历史
- RESOURCE_CANVAS_SECTION_ORDER 改为 PROJECT_RESOURCE_CANVAS_SECTIONS,删除 RESOURCE_CANVAS_VISIBLE_SECTION_ORDER
- reconcileResourceCanvasLayout 接入读时归一化:旧栏目值按资源当前分区改写 section,坐标与手动标记原样保留
- 归并后的布局与原布局逐项不同,由既有协调路径判定 changed 并写回一次新分区,不引入迁移脚本
- 投影层五个资源入表点统一走 projectResourceCanvasCategory:项目版本独立成栏、Agent 回执归文档、其余按资产分类
- 新增 projectResourceDisplayKind 收敛 manifest 资产 subtype 即 kind 的显示类型口径,projectResourceTypeLabel 不再依赖旧栏目
- 卡片预览、编辑分流、媒体工具条、预览文案与画布历史快照改用显示类型与新分区,历史快照入栈时收窄到现行分区
- index.tsx 删除可见栏目导入与 visibleCategoryOrder,栏目标签由资源筛选唯一中文口径派生并补齐 7 栏
- index.tsx 补齐 7 栏默认视口与 7 个栏目图标(新增 LayoutGrid、Users、PackageOpen,移除已无用的 Code2)
- index.tsx 删除 canvasResources 的 code 过滤:只登记游戏代码的项目现在落在待归类栏目并正常显示卡片
- 新增只登记游戏代码项目的 UI 回归用例,覆盖栏目大纲、默认落地栏目与代码卡片渲染
- 测试夹具按新分区轴迁移,新增旧栏目 sidecar 读时归并用例与栏目顺序用例
- 共享测试 harness 的资源卡查询正则允许栏目标签自带空格(UI 交互)
- 同步 PRD 与共享记忆决策记录的栏目口径描述
2026-09-10 20:41:19 +08:00

216 lines
6.3 KiB
TypeScript
Raw 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'))
* 与「下载」(资源面板同一条原生保存对话框 + `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('角色资源只放行快速编辑与下载,不再放行空回调的角色动画动作', () => {
expect(
toolbarActions(
createManifest(),
createResource({ id: 'asset:hero-action', subtype: 'character' }),
),
).toEqual(['quick-edit', '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('拿不到本地文件路径的资源不放行下载,避免渲染出点了报错的按钮', () => {
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');
});
});