import { describe, expect, it } from 'vitest'; import { getSeparationOverview, nodeHasBlockedComponents, nodeHasPendingSeparation, nodeNeedsComponentReview, } from '../src/features/ui-editor/separationOverview'; import { applySeparationProblematicStatuses, clearSeparationComponentStatus, } from '../src/features/ui-editor/separationStatus'; import { getNextMatchingUiTreeNodeTarget } from '../src/features/ui-editor/stageStatusOverview'; import type { Component } from '../src/features/ui-editor/types/Component'; import type { Node } from '../src/features/ui-editor/types/Node'; import type { SpriteAsset } from '../src/features/ui-editor/types/SpriteAsset'; import type { UITree } from '../src/features/ui-editor/types/UITree'; function image(targetGraphic: string | null): Component { return { Image: { target_graphic: targetGraphic, image_type: { Simple: { preserve_aspect: false } }, }, }; } function text(font: 'SystemFont' | { Bound: string }): Component { return { Text: { content: '文本', font, font_style: 'Normal', font_sizing: { Fixed: 14 }, color: [255, 255, 255, 255], alignment: 'UpperLeft', horizontal_overflow: 'Wrap', vertical_overflow: 'Truncate', line_spacing: 1, }, }; } function node( id: string, component: Component | null, componentStatus: Node['metadata']['component_status'] = 'NoProblem', children: Node[] = [], ): Node { return { id, layout: { transform: { anchor_min: [0, 0], anchor_max: [1, 1], offset_min: [0, 0], offset_max: [0, 0], }, custom_minimum_size: [0, 0], size_flags_horizontal: 1, size_flags_vertical: 1, size_flags_stretch_ratio: 1, container: 'None', }, metadata: { name: id, description: '', layout_status: 'NoProblem', component_status: componentStatus, allow_llm_edit_layout: true, allow_llm_edit_component: true, source: 'System', }, component, children, }; } function sprite(id: string): SpriteAsset { return { asset_id: id, metadata: { name: id, asset_type: 'Normal' }, path: `${id}.png`, pixel_size: [16, 16], pixels_per_unit: 1, border: { left: 0, right: 0, top: 0, bottom: 0 }, }; } const sprites = { validSprite: sprite('validSprite'), unused: sprite('unused'), }; const trees: UITree[] = [ { src_ui_design: 'page-a', root: node('root', image('validSprite'), 'NoProblem', [ node('review', image(null), { NeedReview: '确认素材' }), node('blocked', text('SystemFont'), { Blocked: '素材绑定失败' }), ]), }, ]; describe('getSeparationOverview', () => { it('uses per-component helpers to include both image and text slots', () => { expect(getSeparationOverview(trees, sprites)).toEqual({ componentsNeedingAssets: 2, assetSlots: 2, boundSlots: 1, pendingSlots: 1, needsAttention: 2, blocked: 1, independentAssets: 2, }); }); it('reuses the common preorder next-target search for every separation queue', () => { expect( getNextMatchingUiTreeNodeTarget(trees, null, (target) => nodeHasPendingSeparation(target), )?.node.id, ).toBe('review'); expect( getNextMatchingUiTreeNodeTarget(trees, 'review', (target) => nodeHasPendingSeparation(target), )?.node.id, ).toBe('review'); expect( getNextMatchingUiTreeNodeTarget(trees, null, nodeNeedsComponentReview) ?.node.id, ).toBe('review'); expect( getNextMatchingUiTreeNodeTarget(trees, null, nodeHasBlockedComponents) ?.node.id, ).toBe('blocked'); }); }); describe('separation status writeback', () => { it('writes problematic history into NeedReview and overwrites Blocked', () => { const problematicTree: UITree[] = [ { src_ui_design: 'page-a', root: node('root', null, 'NoProblem', [ node('problematic', image(null), { Blocked: '旧阻塞' }), ]), }, ]; expect( applySeparationProblematicStatuses(problematicTree, [ { node_id: 'problematic', problem_description: '当前建议', problem_history: ['第一次建议', '最后建议'], rework_count: 3, }, ]), ).toEqual([]); expect( problematicTree[0].root.children[0]?.metadata.component_status, ).toEqual({ NeedReview: '自动切分重试已达上限(3 次)\n第一次建议\n最后建议', }); expect(getSeparationOverview(problematicTree, {})).toMatchObject({ needsAttention: 1, blocked: 0, }); }); it('clears the old status after a successful bound writeback', () => { const boundNode = node('bound', image(null), { Blocked: '旧阻塞' }); clearSeparationComponentStatus(boundNode); expect(boundNode.metadata.component_status).toBe('NoProblem'); }); });