alarmOperations.test.mjs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import test from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import { readFile, access } from 'node:fs/promises'
  4. import { alarmCurveChoices, auditStatusTone, monitorStatusTone, groupAlarmDevices, findAlarmMenu, seriesPoints, rangeParams } from '../src/views/subSystem/alarm/alarmOperationsModel.js'
  5. test('detail status colors distinguish monitoring and audit states without treating missing values as zero',()=>{
  6. assert.equal(monitorStatusTone(0),'danger')
  7. assert.equal(monitorStatusTone('1'),'success')
  8. assert.equal(auditStatusTone(0),'warning')
  9. assert.equal(auditStatusTone('1'),'primary')
  10. assert.equal(auditStatusTone(2),'info')
  11. for(const status of [null,undefined,'',9]){
  12. assert.equal(monitorStatusTone(status),'info')
  13. assert.equal(auditStatusTone(status),'info')
  14. }
  15. })
  16. test('detail uses lightweight inline state labels and small tags, without old gray status cards',async()=>{
  17. const source=await readFile('src/views/subSystem/alarm/AlarmDetail.vue','utf8')
  18. for(const label of ['监测状态','审核结果','处置状态']) assert.ok(source.includes('<dt>'+label+'</dt>'))
  19. assert.equal((source.match(/size="small" effect="light"/g)||[]).length,3)
  20. assert.ok(source.includes('font-weight: 400'))
  21. assert.ok(source.includes('orderStatusType(alarm.orderStatus)'))
  22. assert.ok(!source.includes('<label>'))
  23. const styles=await readFile('src/views/subSystem/alarm/alarmOperations.scss','utf8')
  24. assert.ok(!styles.includes('.state-grid'))
  25. })
  26. test('curve choices exclude absent values, retain zero and deduplicate events without changing source rows',()=>{
  27. const rows=[{id:'null',actualValue:null},{id:'missing'},{id:'empty',actualValue:''},
  28. {id:'zero',actualValue:0},{id:'negative',actualValue:-1},{id:'text-zero',actualValue:'0'},
  29. {id:'zero',actualValue:0,warningType:'流量'}]
  30. const choices=alarmCurveChoices(rows)
  31. assert.deepEqual(choices.map(row=>row.id),['zero','negative','text-zero'])
  32. assert.equal(choices[0].warningType,'流量')
  33. assert.equal(rows.length,7)
  34. assert.deepEqual(alarmCurveChoices([]),[])
  35. })
  36. test('reminders request only unhandled alarms and no longer link to ledger',async()=>{
  37. const source=await readFile('src/views/subSystem/alarm/remind.vue','utf8')
  38. assert.match(source,/getAlarmReminders\(\{ \.\.\.scope.value, alarmStatus: 0/)
  39. assert.ok(!source.includes('报警清单'))
  40. assert.ok(!source.includes("navigate('alarmList'"))
  41. assert.ok(source.includes('事件详情'))
  42. })
  43. test('ledger labels are updated and the audit filter is removed without removing its table column',async()=>{
  44. const source=await readFile('src/views/subSystem/alarm/list.vue','utf8')
  45. assert.ok(source.includes('placeholder="请输入设备编码/名称"'))
  46. assert.ok(source.includes('<el-form-item label="报警时间">'))
  47. assert.ok(!source.includes('<el-form-item label="审核结果">'))
  48. assert.ok(!source.includes('filters.auditStatus'))
  49. assert.ok(!source.includes('auditStatus:'))
  50. assert.ok(source.includes('prop="auditStatusName" label="审核结果"'))
  51. })
  52. test('GIS filters curve options and entries without filtering map or alarm history',async()=>{
  53. const source=await readFile('src/views/subSystem/alarm/drainage/alarmGIS.vue','utf8')
  54. assert.ok(source.includes('alarmCurveChoices([...(selected.value?.alarms || []), ...history.value])'))
  55. assert.ok(source.includes('device.alarms.find(hasActualValue)'))
  56. assert.ok(source.includes('v-if="hasActualValue(row)"'))
  57. assert.ok(source.includes('watch(curveChoices'))
  58. assert.ok(source.includes("tab === 'curve' && currentCurve"))
  59. assert.ok(source.includes('暂无有实际值的报警指标'))
  60. assert.ok(!source.includes('actualValueOnly: true'))
  61. })
  62. test('analysis filters on server before pagination and rejects empty-value deep links',async()=>{
  63. const source=await readFile('src/views/subSystem/alarm/drainage/analysis.vue','utf8')
  64. assert.ok(source.includes('actualValueOnly: true'))
  65. assert.ok(source.includes('alarmCurveChoices(result.data.records || [])'))
  66. assert.ok(source.includes('if (!hasActualValue(result.data))'))
  67. assert.ok(source.indexOf('if (!hasActualValue(result.data))') < source.indexOf('choices.value.unshift(result.data)'))
  68. const detail=await readFile('src/views/subSystem/alarm/AlarmDetail.vue','utf8')
  69. assert.ok(detail.includes("alarm && hasActualValue(alarm) && menu('drainageAlarmAnalysis')"))
  70. })
  71. test('GIS groups events by device and uses highest current grade, ignoring recovered severity',()=>{
  72. const [device] = groupAlarmDevices([
  73. {id:'a',equipmentId:'E1',deviceCode:'D1',createTime:'2026-01-01',alarmLevel:1,alarmStatus:1,longitude:'110',latitude:'28'},
  74. {id:'b',equipmentId:'E1',deviceCode:'D1',createTime:'2026-01-02',alarmLevel:3,alarmStatus:0,longitude:'110',latitude:'28'},
  75. {id:'c',equipmentId:'E1',deviceCode:'D1',createTime:'2026-01-03',alarmLevel:2,alarmStatus:0,longitude:'110',latitude:'28'}
  76. ])
  77. assert.equal(device.alarms.length,3); assert.equal(device.alarmLevel,2); assert.equal(device.alarms[0].id,'c'); assert.equal(device.hasCoordinates,true)
  78. })
  79. test('GIS keeps missing or invalid coordinates in device list but does not map them',()=>{
  80. for(const [longitude,latitude] of [[null,28],['',28],[undefined,28],['nan',28],[181,28],[110,91],[0,0]]){
  81. const [device]=groupAlarmDevices([{deviceCode:'D1',longitude,latitude}]); assert.equal(device.hasCoordinates,false)
  82. }
  83. assert.equal(groupAlarmDevices([{deviceCode:'D1',longitude:0,latitude:28}])[0].hasCoordinates,true)
  84. })
  85. test('same leaf route selects only the matching authorized subsystem',()=>{
  86. const routes=[{path:'/water',children:[{path:'alarmList',query:'{"rootTypeId":1}'}]},{path:'/drainage',children:[{path:'alarmList',query:'{"rootTypeId":2}'}]}]
  87. assert.equal(findAlarmMenu(routes,'alarmList',2).path,'/drainage/alarmList')
  88. assert.equal(findAlarmMenu(routes,'alarmList',3),null)
  89. assert.equal(findAlarmMenu([{path:'alarmList',query:'invalid'}],'alarmList',2),null)
  90. })
  91. test('menu navigation reads the real permission store sidebar property',async()=>{
  92. const scope=await readFile('src/views/subSystem/alarm/useAlarmScope.js','utf8')
  93. const store=await readFile('src/store/modules/permission.js','utf8')
  94. const property=scope.match(/findAlarmMenu\(permissions\.(\w+)/)[1]
  95. assert.match(store,new RegExp(property+': \\[\\]'))
  96. })
  97. test('raw series preserve zero and missing values and break large sampling gaps',()=>{
  98. const points=seriesPoints([{time:'2026-01-01 00:00:00',value:0},{time:'2026-01-01 00:05:00',value:2},{time:'2026-01-01 00:10:00',value:null},{time:'2026-01-01 01:00:00',value:4}],true)
  99. assert.deepEqual(points.map(p=>p[1]),[0,2,null,null,4])
  100. assert.equal(seriesPoints([{time:'2026-01-01 00:00:00',value:0}],true)[0][1],0)
  101. })
  102. test('new GIS entry has the correct name, three tabs and no surrounding environment tab',async()=>{
  103. const source=await readFile('src/views/subSystem/alarm/drainage/alarmGIS.vue','utf8')
  104. assert.ok(source.includes('DrainageAlarmGIS')); assert.ok(!source.includes('周边环境'))
  105. for(const label of ['设备信息','报警记录','监测曲线'])assert.ok(source.includes('label="'+label+'"'))
  106. await assert.rejects(access('src/views/subSystem/alarm/drainage/alarmMap.vue'))
  107. })
  108. test('GIS prototype layout keeps the map width stable with a separate responsive detail drawer',async()=>{
  109. const source=await readFile('src/views/subSystem/alarm/drainage/alarmGIS.vue','utf8')
  110. const styles=await readFile('src/views/subSystem/alarm/drainage/alarmGIS.scss','utf8')
  111. for(const text of ['gis-page-heading','gis-level-metrics','gis-device-pane','gis-map-status','gis-map-legend','gis-workspace-footer'])assert.ok(source.includes(text))
  112. assert.ok(source.includes('<el-drawer v-model="detailVisible"'))
  113. assert.ok(source.includes('append-to-body destroy-on-close'))
  114. assert.ok(!source.includes('selection-panel'))
  115. assert.ok(source.includes('device.alarmLevel === Number(item.value)'))
  116. assert.ok(source.includes('getAlarmMap({ ...scope.value'))
  117. assert.ok(source.includes('pageNum: historyPage.value, pageSize: historySize.value'))
  118. assert.ok(source.includes('aria-label="查看全部点位"'))
  119. assert.ok(styles.includes('grid-template-columns: 300px minmax(0, 1fr)'))
  120. assert.ok(styles.includes('@media (max-width: 680px)'))
  121. })
  122. test('ledger uses first-column business number, server pagination and complete-day dates',async()=>{
  123. const source=await readFile('src/views/subSystem/alarm/list.vue','utf8')
  124. assert.match(source,/<el-table-column prop="alarmNo" label="报警单号"/)
  125. assert.ok(source.indexOf('prop="alarmNo"')<source.indexOf('prop="deviceCode"'))
  126. assert.ok(source.includes('pageNum: pageNum.value, pageSize: pageSize.value'))
  127. assert.deepEqual(rangeParams(['2026-01-01','2026-01-02']),{startTime:'2026-01-01 00:00:00',endTime:'2026-01-02 23:59:59.999999'})
  128. })
  129. test('replaced subsystem entries are removed',async()=>{
  130. for(const name of ['drainage/jcbj/bjtx.vue','drainage/jcbj/bjyp.vue','drainage/jcbj/bjqd.vue','drainage/jcbj/dqbj.vue','waterSupply/monitorAlarm/index.vue','waterSupply/wjcbj/bjgl.vue']) await assert.rejects(access('src/views/subSystem/'+name))
  131. })