import { describe, expect, it } from 'vitest'; import { getBindingOverview, nodeHasBlockedComponents, nodeHasPendingBinding, nodeNeedsComponentReview, } from '../src/features/ui-editor/bindingOverview'; 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, components: Component[], componentsStatus: Node['metadata']['components_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', components_status: componentsStatus, allow_llm_edit_layout: true, allow_llm_edit_component: true, source: 'System', }, components, 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'), text({ Bound: 'font-id' })], 'NoProblem', [ node('review', [image(null)], { NeedReview: '确认素材' }), node('blocked', [text('SystemFont')], { Blocked: '素材绑定失败' }), ], ), }, ]; describe('getBindingOverview', () => { it('uses per-component helpers to include both image and text slots', () => { expect(getBindingOverview(trees, sprites)).toEqual({ componentsNeedingAssets: 3, assetSlots: 3, boundSlots: 2, pendingSlots: 1, needsAttention: 2, blocked: 1, independentAssets: 2, }); }); it('reuses the common preorder next-target search for every binding queue', () => { expect( getNextMatchingUiTreeNodeTarget(trees, null, (target) => nodeHasPendingBinding(target), )?.node.id, ).toBe('review'); expect( getNextMatchingUiTreeNodeTarget(trees, 'review', (target) => nodeHasPendingBinding(target), )?.node.id, ).toBe('review'); expect( getNextMatchingUiTreeNodeTarget(trees, null, nodeNeedsComponentReview) ?.node.id, ).toBe('review'); expect( getNextMatchingUiTreeNodeTarget(trees, null, nodeHasBlockedComponents) ?.node.id, ).toBe('blocked'); }); });