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([ ['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(), ), ).toBeNull(); }); });