/** T45 重叠设备聚合与展开纯逻辑检查(票 45;规格 17)。 */ import { DEFAULT_MAX_FAN, OVERLAP_STATE, aggregateMarker, fanLayout, groupOverlappingDevices, isFanCandidate, overlapCount, resolveOverlapPick, shouldCollapseOverlap, transitionOverlapState, } from '../../../src/modules/cockpit/domain/overlapDevices.js' const checks = [] function check(name, pass, detail = '') { checks.push({ name, pass }) console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`) } const row = (code, overrides = {}) => ({ code, pointCode: 'MP-1', longitude: 110.4, latitude: 28.44, status: 'NORMAL', placement: 'drawn', ...overrides, }) // K/N 分类与坐标/模式/待确认排除 { const rows = [ row('EQ-1'), row('EQ-2', { longitude: 110.400001, status: 'ALARM' }), row('EQ-3', { placement: 'no-location' }), row('EQ-4', { pointCode: 'MP-2' }), ] const groups = groupOverlappingDevices(rows) const counts = overlapCount({ groups, total: 4 }) check('A1-overlap-groups-by-point-and-distance', groups.length === 2 && groups[0].k === 2 && groups[1].k === 1, `组 ${groups.map((g) => `${g.pointCode}:${g.k}`).join('/')}`) check('A2-k-excludes-nonrenderable', counts.k === 3 && counts.n === 4, `K=${counts.k} N=${counts.n}`) check('A3-fan-candidate-requires-drawn-coordinate', isFanCandidate(row('EQ-X')) && !isFanCandidate(row('EQ-Y', { placement: 'hidden' }))) } // 最高状态与聚合符号不冒充设备 { const [group] = groupOverlappingDevices([row('EQ-1'), row('EQ-2', { status: 'ALARM' })]) const marker = aggregateMarker(group) check('B1-highest-status-wins', group.visual.status === 'ALARM' && group.visual.shape === 'triangle', `${group.visual.status}/${group.visual.shape}`) check('B2-aggregate-has-no-device-code', marker.deviceCode === '' && marker.code === '' && marker.kind === 'monitoring-device-overlap') check('B3-badge-is-k', marker.k === 2 && marker.n === 2 && marker.label.includes('2')) } // K > maxFan 降级 { const rows = Array.from({ length: 11 }, (_, index) => row(`EQ-${index}`, { longitude: 110.4 + index * 0.000001 })) const [group] = groupOverlappingDevices(rows) check('C1-default-max-fan-is-10', DEFAULT_MAX_FAN === 10 && group.degraded && group.maxFan === 10) check('C2-degraded-label-tells-remainder', group.state === OVERLAP_STATE.DEGRADED && group.label.includes('其余见清单')) const custom = groupOverlappingDevices(rows, { maxFan: 5 })[0] check('C3-configurable-max-fan', custom.maxFan === 5 && custom.degraded) } // 展开态与三种收起条件 { check('D1-hover-preview-expands', transitionOverlapState('collapsed', 'hover-enter') === 'expanded') check('D2-hover-leave-collapses-preview', transitionOverlapState('expanded', 'hover-leave') === 'collapsed') check('D3-click-is-sticky-expanded', transitionOverlapState('collapsed', 'aggregate-click') === 'expanded') check('D4-device-blank-back-all-collapse', ['device-click', 'blank-click', 'back-to-point'].every((action) => shouldCollapseOverlap(action) && transitionOverlapState('expanded', action) === 'collapsed')) } // 屏幕分离不污染真实位置,且展开子点有设备身份 { const [group] = groupOverlappingDevices([row('EQ-1'), row('EQ-2', { longitude: 110.400001 })]) const fan = fanLayout(group, { center: { x: 100, y: 100 }, radius: 40 }) check('E1-fan-has-device-identities', fan.length === 2 && fan.every((item) => item.deviceCode && item.kind === 'monitoring-device')) check('E2-fan-does-not-change-space-coordinates', fan[0].longitude === group.rows[0].longitude && fan[0].latitude === group.rows[0].latitude) check('E3-fan-positions-are-screen-only', fan.some((item) => item.x !== 100 || item.y !== 100) && fan.every((item) => Number.isFinite(item.x) && Number.isFinite(item.y))) } // 普通拾取:设备优先、聚合符号无设备身份 { const aggregate = { kind: 'monitoring-device-overlap', deviceCode: '' } const device = { kind: 'monitoring-device', deviceCode: 'EQ-1' } check('F1-device-pick-precedes-aggregate-and-point', resolveOverlapPick({ aggregateHit: aggregate, deviceHit: device, pointHit: { kind: 'monitoring-point', code: 'MP-1' } }).target === 'device') check('F2-aggregate-pick-is-not-device', resolveOverlapPick({ aggregateHit: aggregate, pointHit: { kind: 'monitoring-point', code: 'MP-1' } }).target === 'overlap') } const failed = checks.filter((item) => !item.pass) console.log('') console.log(`T45 重叠设备聚合检查:${checks.length - failed.length}/${checks.length} 项通过。`) if (failed.length) { console.log('未通过:') for (const item of failed) console.log(` · ${item.name}`) process.exit(1) } console.log('T45 重叠设备聚合全部通过。')