2f8753f597
- 契约层把 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 与共享记忆决策记录的栏目口径描述
235 lines
6.4 KiB
TypeScript
235 lines
6.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
canonicalProjectedResourceMediaType,
|
|
resolveProjectCharacterAnimationCapability,
|
|
resolveProjectResourceEditCapability,
|
|
resourceEditPromptMaxLength,
|
|
} from '../src/view/project-development/resourceEditModel';
|
|
import type { ProjectResource } from '../src/view/project-development/resourceProjectionModel';
|
|
|
|
function resource(
|
|
input: Partial<ProjectResource> &
|
|
Pick<ProjectResource, 'id' | 'category' | 'path'>,
|
|
): ProjectResource {
|
|
return {
|
|
subtype: 'task-artifact',
|
|
label: input.path,
|
|
mediaType: '',
|
|
sourceLabel: '测试来源',
|
|
taskTitle: null,
|
|
manifestAssetId: null,
|
|
producerTaskId: 'task-1',
|
|
externalResourceId: null,
|
|
referenceResourceIds: [],
|
|
dependencies: [],
|
|
dependencyDepth: 0,
|
|
...input,
|
|
};
|
|
}
|
|
|
|
describe('全类型资源编辑能力模型', () => {
|
|
it('把现役资源稳定分流到图片派生、媒体派生、文本派生和版本分支', () => {
|
|
const cases: Array<{
|
|
resource: ProjectResource;
|
|
route: 'derive';
|
|
editKind?: string;
|
|
}> = [
|
|
{
|
|
resource: resource({
|
|
id: 'asset:png',
|
|
category: 'unclassified',
|
|
path: 'assets/hero.png',
|
|
mediaType: 'image/png',
|
|
manifestAssetId: 'png',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'image-reference',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'task:art:hero.webp',
|
|
category: 'unclassified',
|
|
path: 'hero.webp',
|
|
mediaType: '美术产物',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'image-reference',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'asset:gif',
|
|
category: 'unclassified',
|
|
path: 'assets/hero.gif',
|
|
mediaType: 'image/gif',
|
|
manifestAssetId: 'gif',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'image-reference',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'asset:svg',
|
|
category: 'unclassified',
|
|
path: 'assets/icon.svg',
|
|
mediaType: 'image/svg+xml',
|
|
manifestAssetId: 'svg',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'svg',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'task:art:intro.mp4',
|
|
category: 'unclassified',
|
|
path: 'assets/intro.mp4',
|
|
mediaType: '美术产物',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'video',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'asset:bgm',
|
|
category: 'audio',
|
|
path: 'audio/theme.ogg',
|
|
subtype: 'background-music',
|
|
mediaType: 'audio/ogg',
|
|
manifestAssetId: 'bgm',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'background-music',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'asset:sfx',
|
|
category: 'audio',
|
|
path: 'audio/hit.wav',
|
|
subtype: 'sound-effect',
|
|
mediaType: 'audio/wav',
|
|
manifestAssetId: 'sfx',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'sound-effect',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'task:code:main.ts',
|
|
category: 'document',
|
|
path: 'src/main.ts',
|
|
mediaType: '项目文档',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'text',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'agent-result:design:message-1',
|
|
category: 'document',
|
|
path: '专业 Agent 文本回执',
|
|
subtype: 'agent-result',
|
|
producerTaskId: null,
|
|
content: '回执',
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'agent-result',
|
|
},
|
|
{
|
|
resource: resource({
|
|
id: 'version:v1',
|
|
category: 'version',
|
|
path: '项目版本 · v1',
|
|
subtype: 'project-version',
|
|
producerTaskId: null,
|
|
version: {
|
|
versionId: 'v1',
|
|
parentVersionId: null,
|
|
projectRevision: 1,
|
|
resourceBindings: [],
|
|
createdReason: 'initial',
|
|
createdAt: 1,
|
|
label: '版本 1',
|
|
childVersionIds: [],
|
|
},
|
|
}),
|
|
route: 'derive',
|
|
editKind: 'version',
|
|
},
|
|
];
|
|
|
|
for (const expected of cases) {
|
|
const capability = resolveProjectResourceEditCapability(
|
|
expected.resource,
|
|
);
|
|
expect(capability.route, expected.resource.id).toBe(expected.route);
|
|
expect(capability.editKind, expected.resource.id).toBe(expected.editKind);
|
|
}
|
|
});
|
|
|
|
it('用路径补齐任务产物缺失的标准媒体类型', () => {
|
|
expect(
|
|
canonicalProjectedResourceMediaType(
|
|
resource({
|
|
id: 'task:art:clip.mov',
|
|
category: 'unclassified',
|
|
path: 'assets/clip.mov',
|
|
mediaType: '美术产物',
|
|
}),
|
|
),
|
|
).toBe('video/quicktime');
|
|
expect(
|
|
canonicalProjectedResourceMediaType(
|
|
resource({
|
|
id: 'task:audio:effect.wav',
|
|
category: 'audio',
|
|
path: 'audio/effect.wav',
|
|
mediaType: '美术产物',
|
|
}),
|
|
),
|
|
).toBe('audio/wav');
|
|
expect(
|
|
canonicalProjectedResourceMediaType(
|
|
resource({
|
|
id: 'task:code:main.ts',
|
|
category: 'document',
|
|
path: 'src/main.ts',
|
|
mediaType: '项目文档',
|
|
}),
|
|
),
|
|
).toBe('text/typescript');
|
|
});
|
|
|
|
it('按现役媒体接口限制编辑提示词长度', () => {
|
|
expect(resourceEditPromptMaxLength('background-music')).toBe(140);
|
|
expect(resourceEditPromptMaxLength('sound-effect')).toBe(1_900);
|
|
expect(resourceEditPromptMaxLength('video')).toBe(4_000);
|
|
expect(resourceEditPromptMaxLength('character-animation')).toBe(4_000);
|
|
expect(resourceEditPromptMaxLength('text')).toBe(32_000);
|
|
});
|
|
|
|
it('图片资源可进入角色动画派生入口', () => {
|
|
const capability = resolveProjectCharacterAnimationCapability(
|
|
resource({
|
|
id: 'asset:hero',
|
|
category: 'unclassified',
|
|
path: 'assets/hero.png',
|
|
mediaType: 'image/png',
|
|
manifestAssetId: 'hero',
|
|
}),
|
|
);
|
|
expect(capability?.editKind).toBe('character-animation');
|
|
expect(capability?.generationMode).toBe('derive');
|
|
expect(
|
|
resolveProjectCharacterAnimationCapability(
|
|
resource({
|
|
id: 'asset:bgm',
|
|
category: 'audio',
|
|
path: 'audio/theme.mp3',
|
|
mediaType: 'audio/mpeg',
|
|
manifestAssetId: 'bgm',
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
});
|
|
});
|