d1581892d7
- 素材重命名(跨范围契约):`rename_local_project_asset` 现在要求 `expectedProjectId` + `expectedProjectRevision`,前端按删除 / 分类保存同一套 CAS 口径补齐——先 `get_local_game_project_revision` 读当前 revision 再连同项目身份提交,不用可能过期的本地缓存值 - 失败文案统一:新增 `projectAssetCommandErrorMessage`,把 `project-identity-conflict` / `project-revision-conflict` 翻成用户可读中文,重命名 / 删除 / 分类保存三条链路共用一份映射,其余错误原样透出 - index.tsx:资源面板下载按 `resolveDownloadablePanelEntries` 过滤,版本卡这类合成 path 的条目不再进入落盘链路 - index.tsx:`characterAnimationPanel` 纳入画布浮层开关判据(抽 `resolveResourceCanvasFloatingPanelOpen`),「生成动画」面板与快速编辑 / 信息浮层共用同一条「点外部 / Esc 关闭」规则 - index.tsx:去掉 `ResourceBookScene` 上重复的 `onWheel`,滚轮只由 manager 上的原生 `passive:false` 监听处理,平移 / 缩放不再被翻倍 - index.tsx:快速编辑的源资源在层 id 命中不到时回落到已正规化的 `asset:<id>`,重试不再静默什么都不做;取不到时给出可见失败 - ResourceCanvasPanelView.tsx:下载按钮按可下载条数判可用、不再在上传中显示下载转圈;上传中禁掉 × / Esc / 遮罩三条关闭路径;预览占位文案抽 helper - GameRunVersionPicker.tsx:portal 菜单在滚动 / 缩放后重算位置;外部点击判定沿用共享 `useImageCanvasFloatingOptionDismiss` + `menuRef` 边界(review 该条已修,保留现状并补位置用例) - ResourceCanvasGenerationPanelView.tsx:`submitting` 改 `finally` 收回,成功路径不再永久锁住面板 - ResourceAssetDeleteDialog.tsx / ResourceRenameDialog.tsx / ResourceClassificationPanel.tsx:在飞时 `closeOnEscape` / `closeOnBackdrop` 与头部 × 一起挡住 - resourceEditModel.ts / resourceCanvasToolbarModel.ts / useProjectResourceCardPreviews.ts / projectResourceLiveUpdateModel.ts:抽 `resourceBaseName`、`canonicalProjectedResourceMediaType` 参数收窄去掉强转、删未使用参数、补「IO 失败会 reject」的签名说明 - resourceCanvasHistoryModel.ts:`resourceCanvasSnapshotsEqual` 不再比较不可恢复的 `manuallyPlaced`,消除「压进历史但撤销是空操作」 - resourceCanvasSectionMapping.ts:删掉从未接线、且注释与实现相矛盾的 `LEGACY_RESOURCE_CANVAS_SECTION_FALLBACK` - resourceCanvasChrome.css:`.game-resource-panel-grid` 补 `flex: 1 1 auto; min-height: 0`,卡片网格成为真正的滚动区 - 用例:重命名 CAS 载荷与读序、CAS 拒绝文案、版本卡下载门禁、上传中关闭路径、浮层判据、菜单跟随滚动、分区映射
207 lines
6.5 KiB
TypeScript
207 lines
6.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
LEGACY_PROJECT_RESOURCE_CANVAS_SECTIONS,
|
|
PROJECT_RESOURCE_CANVAS_SECTIONS,
|
|
type ProjectResourceCanvasCategory,
|
|
} from '../../../packages/shared/src/contracts/gameCreationApp';
|
|
import {
|
|
LEGACY_RESOURCE_CANVAS_SECTION_TARGETS,
|
|
normalizeResourceCanvasPosition,
|
|
resolveResourceCanvasSection,
|
|
} from '../src/view/project-development/resourceCanvasSectionMapping';
|
|
|
|
function categoryByResourceId(category: ProjectResourceCanvasCategory) {
|
|
return new Map<string, ProjectResourceCanvasCategory>([
|
|
['resource-1', category],
|
|
]);
|
|
}
|
|
|
|
describe('资源画布分区轴与新旧栏目映射', () => {
|
|
it('现行分区为 6 类资产分类加末尾的项目版本栏目', () => {
|
|
expect(PROJECT_RESOURCE_CANVAS_SECTIONS).toEqual([
|
|
'ui-interaction',
|
|
'character',
|
|
'scene',
|
|
'audio',
|
|
'document',
|
|
'unclassified',
|
|
'version',
|
|
]);
|
|
});
|
|
|
|
it('每个旧栏目值都有归并目标,且目标都落在现行分区里', () => {
|
|
for (const legacy of LEGACY_PROJECT_RESOURCE_CANVAS_SECTIONS) {
|
|
const targets = LEGACY_RESOURCE_CANVAS_SECTION_TARGETS[legacy];
|
|
expect(targets.length).toBeGreaterThan(0);
|
|
for (const target of targets) {
|
|
expect(PROJECT_RESOURCE_CANVAS_SECTIONS).toContain(target);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('旧栏目坐标按资源当前分区归并,x / y / manuallyPlaced 原样保留', () => {
|
|
const position = {
|
|
resourceId: 'resource-1',
|
|
section: 'art' as const,
|
|
x: -24,
|
|
y: 96,
|
|
manuallyPlaced: true,
|
|
};
|
|
|
|
for (const legacy of LEGACY_PROJECT_RESOURCE_CANVAS_SECTIONS) {
|
|
for (const target of LEGACY_RESOURCE_CANVAS_SECTION_TARGETS[legacy]) {
|
|
const normalized = normalizeResourceCanvasPosition(
|
|
{ ...position, section: legacy },
|
|
categoryByResourceId(target),
|
|
);
|
|
expect(normalized).toEqual({
|
|
position: {
|
|
resourceId: 'resource-1',
|
|
section: target,
|
|
x: -24,
|
|
y: 96,
|
|
manuallyPlaced: true,
|
|
},
|
|
changed: legacy !== target,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
it('旧 art 是扩展名口径,按资源当前分区落入四类资产栏目', () => {
|
|
expect(LEGACY_RESOURCE_CANVAS_SECTION_TARGETS.art).toEqual([
|
|
'ui-interaction',
|
|
'character',
|
|
'scene',
|
|
'unclassified',
|
|
]);
|
|
for (const target of LEGACY_RESOURCE_CANVAS_SECTION_TARGETS.art) {
|
|
expect(resolveResourceCanvasSection('art', target)).toBe(target);
|
|
}
|
|
// 目标集合之外不猜、也不回落到别的栏目:宁可让这条坐标走自动排布,
|
|
// 也不把资源放进它并不属于的栏目。
|
|
expect(resolveResourceCanvasSection('art', 'document')).toBeNull();
|
|
});
|
|
|
|
it('旧 code 统一归入待归类', () => {
|
|
expect(LEGACY_RESOURCE_CANVAS_SECTION_TARGETS.code).toEqual([
|
|
'unclassified',
|
|
]);
|
|
expect(resolveResourceCanvasSection('code', 'unclassified')).toBe(
|
|
'unclassified',
|
|
);
|
|
expect(resolveResourceCanvasSection('code', 'document')).toBeNull();
|
|
});
|
|
|
|
it('新旧同名的旧值不产生额外写入,独有旧值改写后必然判定为变化', () => {
|
|
for (const legacy of ['document', 'audio', 'version'] as const) {
|
|
const normalized = normalizeResourceCanvasPosition(
|
|
{
|
|
resourceId: 'resource-1',
|
|
section: legacy,
|
|
x: 12,
|
|
y: 34,
|
|
manuallyPlaced: false,
|
|
},
|
|
categoryByResourceId(legacy),
|
|
);
|
|
expect(normalized?.position.section).toBe(legacy);
|
|
expect(normalized?.changed).toBe(false);
|
|
}
|
|
|
|
for (const legacy of ['art', 'code'] as const) {
|
|
const target = LEGACY_RESOURCE_CANVAS_SECTION_TARGETS[legacy][0]!;
|
|
const normalized = normalizeResourceCanvasPosition(
|
|
{
|
|
resourceId: 'resource-1',
|
|
section: legacy,
|
|
x: 12,
|
|
y: 34,
|
|
manuallyPlaced: false,
|
|
},
|
|
categoryByResourceId(target),
|
|
);
|
|
expect(normalized?.position.section).toBe(target);
|
|
expect(normalized?.changed).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('现役分区与资源分区不一致时归并到资源当前分类并保留坐标', () => {
|
|
// 旧实现把"现行栏目值但不等于资源当前分类"判 null、整条坐标丢弃 —— 那等于把用户手摆的
|
|
// 位置换成自动排布。资源分类本来就会变(`kind:"UI"` 的 8 条资产从「待归类」被修到
|
|
// 「UI 交互」就是真机一例),所以这里必须归并,语义与旧栏目归并支路完全一致。
|
|
expect(resolveResourceCanvasSection('scene', 'character')).toBe(
|
|
'character',
|
|
);
|
|
expect(resolveResourceCanvasSection('version', 'document')).toBe(
|
|
'document',
|
|
);
|
|
expect(
|
|
normalizeResourceCanvasPosition(
|
|
{
|
|
resourceId: 'resource-1',
|
|
section: 'scene',
|
|
x: 0,
|
|
y: 0,
|
|
manuallyPlaced: true,
|
|
},
|
|
categoryByResourceId('character'),
|
|
),
|
|
).toEqual({
|
|
position: {
|
|
resourceId: 'resource-1',
|
|
section: 'character',
|
|
x: 0,
|
|
y: 0,
|
|
manuallyPlaced: true,
|
|
},
|
|
changed: true,
|
|
});
|
|
});
|
|
|
|
it('归并到资源当前分类时 x / y / manuallyPlaced 逐项不被改写', () => {
|
|
// 真机现场:`kind:"UI"` 资产落盘在「待归类」栏,分类被修到「UI 交互」之后
|
|
// 它们的坐标必须跟过来,而不是被丢弃后由自动排布重算。
|
|
expect(
|
|
normalizeResourceCanvasPosition(
|
|
{
|
|
resourceId: 'resource-1',
|
|
section: 'unclassified',
|
|
x: -24,
|
|
y: 96,
|
|
manuallyPlaced: true,
|
|
},
|
|
categoryByResourceId('ui-interaction'),
|
|
),
|
|
).toEqual({
|
|
position: {
|
|
resourceId: 'resource-1',
|
|
section: 'ui-interaction',
|
|
x: -24,
|
|
y: 96,
|
|
manuallyPlaced: true,
|
|
},
|
|
changed: true,
|
|
});
|
|
});
|
|
|
|
it('不猜测无法识别的分区取值和未知资源', () => {
|
|
expect(resolveResourceCanvasSection('not-a-section', 'scene')).toBeNull();
|
|
expect(resolveResourceCanvasSection(undefined, 'scene')).toBeNull();
|
|
expect(resolveResourceCanvasSection(null, 'scene')).toBeNull();
|
|
expect(
|
|
normalizeResourceCanvasPosition(
|
|
{
|
|
resourceId: 'missing-resource',
|
|
section: 'scene',
|
|
x: 0,
|
|
y: 0,
|
|
manuallyPlaced: true,
|
|
},
|
|
new Map<string, ProjectResourceCanvasCategory>(),
|
|
),
|
|
).toBeNull();
|
|
});
|
|
});
|