ad8f42a68f
扩展 problematic DTO 携带完整返工历史 分离流程回写 NeedReview 并清理成功节点旧状态 补充概览计数定位与状态覆盖测试 同步 UI workflow 规范文档
176 lines
4.9 KiB
TypeScript
176 lines
4.9 KiB
TypeScript
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');
|
|
});
|
|
});
|