t45-overlap-devices.mjs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /** T45 重叠设备聚合与展开纯逻辑检查(票 45;规格 17)。 */
  2. import {
  3. DEFAULT_MAX_FAN,
  4. OVERLAP_STATE,
  5. aggregateMarker,
  6. fanLayout,
  7. groupOverlappingDevices,
  8. isFanCandidate,
  9. overlapCount,
  10. resolveOverlapPick,
  11. shouldCollapseOverlap,
  12. transitionOverlapState,
  13. } from '../../../src/modules/cockpit/domain/overlapDevices.js'
  14. const checks = []
  15. function check(name, pass, detail = '') {
  16. checks.push({ name, pass })
  17. console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
  18. }
  19. const row = (code, overrides = {}) => ({
  20. code,
  21. pointCode: 'MP-1',
  22. longitude: 110.4,
  23. latitude: 28.44,
  24. status: 'NORMAL',
  25. placement: 'drawn',
  26. ...overrides,
  27. })
  28. // K/N 分类与坐标/模式/待确认排除
  29. {
  30. const rows = [
  31. row('EQ-1'),
  32. row('EQ-2', { longitude: 110.400001, status: 'ALARM' }),
  33. row('EQ-3', { placement: 'no-location' }),
  34. row('EQ-4', { pointCode: 'MP-2' }),
  35. ]
  36. const groups = groupOverlappingDevices(rows)
  37. const counts = overlapCount({ groups, total: 4 })
  38. check('A1-overlap-groups-by-point-and-distance', groups.length === 2 && groups[0].k === 2 && groups[1].k === 1,
  39. `组 ${groups.map((g) => `${g.pointCode}:${g.k}`).join('/')}`)
  40. check('A2-k-excludes-nonrenderable', counts.k === 3 && counts.n === 4, `K=${counts.k} N=${counts.n}`)
  41. check('A3-fan-candidate-requires-drawn-coordinate', isFanCandidate(row('EQ-X')) && !isFanCandidate(row('EQ-Y', { placement: 'hidden' })))
  42. }
  43. // 最高状态与聚合符号不冒充设备
  44. {
  45. const [group] = groupOverlappingDevices([row('EQ-1'), row('EQ-2', { status: 'ALARM' })])
  46. const marker = aggregateMarker(group)
  47. check('B1-highest-status-wins', group.visual.status === 'ALARM' && group.visual.shape === 'triangle', `${group.visual.status}/${group.visual.shape}`)
  48. check('B2-aggregate-has-no-device-code', marker.deviceCode === '' && marker.code === '' && marker.kind === 'monitoring-device-overlap')
  49. check('B3-badge-is-k', marker.k === 2 && marker.n === 2 && marker.label.includes('2'))
  50. }
  51. // K > maxFan 降级
  52. {
  53. const rows = Array.from({ length: 11 }, (_, index) => row(`EQ-${index}`, { longitude: 110.4 + index * 0.000001 }))
  54. const [group] = groupOverlappingDevices(rows)
  55. check('C1-default-max-fan-is-10', DEFAULT_MAX_FAN === 10 && group.degraded && group.maxFan === 10)
  56. check('C2-degraded-label-tells-remainder', group.state === OVERLAP_STATE.DEGRADED && group.label.includes('其余见清单'))
  57. const custom = groupOverlappingDevices(rows, { maxFan: 5 })[0]
  58. check('C3-configurable-max-fan', custom.maxFan === 5 && custom.degraded)
  59. }
  60. // 展开态与三种收起条件
  61. {
  62. check('D1-hover-preview-expands', transitionOverlapState('collapsed', 'hover-enter') === 'expanded')
  63. check('D2-hover-leave-collapses-preview', transitionOverlapState('expanded', 'hover-leave') === 'collapsed')
  64. check('D3-click-is-sticky-expanded', transitionOverlapState('collapsed', 'aggregate-click') === 'expanded')
  65. check('D4-device-click-keeps-selected-entry', !shouldCollapseOverlap('device-click') && transitionOverlapState('expanded', 'device-click') === 'selected')
  66. check('D5-blank-back-close-end-fan', ['blank-click', 'back-to-point', 'close'].every((action) => shouldCollapseOverlap(action) && transitionOverlapState('expanded', action) === 'collapsed'))
  67. check('D6-selected-entry-can-preview-again', transitionOverlapState('selected', 'hover-enter') === 'expanded')
  68. }
  69. // 屏幕分离不污染真实位置,且展开子点有设备身份
  70. {
  71. const [group] = groupOverlappingDevices([row('EQ-1'), row('EQ-2', { longitude: 110.400001 })])
  72. const fan = fanLayout(group, { center: { x: 100, y: 100 }, radius: 40 })
  73. check('E1-fan-has-device-identities', fan.length === 2 && fan.every((item) => item.deviceCode && item.kind === 'monitoring-device'))
  74. check('E2-fan-does-not-change-space-coordinates', fan[0].longitude === group.rows[0].longitude && fan[0].latitude === group.rows[0].latitude)
  75. 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)))
  76. }
  77. // 普通拾取:设备优先、聚合符号无设备身份
  78. {
  79. const aggregate = { kind: 'monitoring-device-overlap', deviceCode: '' }
  80. const device = { kind: 'monitoring-device', deviceCode: 'EQ-1' }
  81. check('F1-device-pick-precedes-aggregate-and-point', resolveOverlapPick({ aggregateHit: aggregate, deviceHit: device, pointHit: { kind: 'monitoring-point', code: 'MP-1' } }).target === 'device')
  82. check('F2-aggregate-pick-is-not-device', resolveOverlapPick({ aggregateHit: aggregate, pointHit: { kind: 'monitoring-point', code: 'MP-1' } }).target === 'overlap')
  83. }
  84. const failed = checks.filter((item) => !item.pass)
  85. console.log('')
  86. console.log(`T45 重叠设备聚合检查:${checks.length - failed.length}/${checks.length} 项通过。`)
  87. if (failed.length) {
  88. console.log('未通过:')
  89. for (const item of failed) console.log(` · ${item.name}`)
  90. process.exit(1)
  91. }
  92. console.log('T45 重叠设备聚合全部通过。')