import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile, access } from 'node:fs/promises'
import { alarmCurveChoices, auditStatusTone, monitorStatusTone, groupAlarmDevices, findAlarmMenu, seriesPoints, rangeParams } from '../src/views/subSystem/alarm/alarmOperationsModel.js'
test('detail status colors distinguish monitoring and audit states without treating missing values as zero',()=>{
assert.equal(monitorStatusTone(0),'danger')
assert.equal(monitorStatusTone('1'),'success')
assert.equal(auditStatusTone(0),'warning')
assert.equal(auditStatusTone('1'),'primary')
assert.equal(auditStatusTone(2),'info')
for(const status of [null,undefined,'',9]){
assert.equal(monitorStatusTone(status),'info')
assert.equal(auditStatusTone(status),'info')
}
})
test('detail uses lightweight inline state labels and small tags, without old gray status cards',async()=>{
const source=await readFile('src/views/subSystem/alarm/AlarmDetail.vue','utf8')
for(const label of ['监测状态','审核结果','处置状态']) assert.ok(source.includes('
'+label+' '))
assert.equal((source.match(/size="small" effect="light"/g)||[]).length,3)
assert.ok(source.includes('font-weight: 400'))
assert.ok(source.includes('orderStatusType(alarm.orderStatus)'))
assert.ok(!source.includes(''))
const styles=await readFile('src/views/subSystem/alarm/alarmOperations.scss','utf8')
assert.ok(!styles.includes('.state-grid'))
})
test('curve choices exclude absent values, retain zero and deduplicate events without changing source rows',()=>{
const rows=[{id:'null',actualValue:null},{id:'missing'},{id:'empty',actualValue:''},
{id:'zero',actualValue:0},{id:'negative',actualValue:-1},{id:'text-zero',actualValue:'0'},
{id:'zero',actualValue:0,warningType:'流量'}]
const choices=alarmCurveChoices(rows)
assert.deepEqual(choices.map(row=>row.id),['zero','negative','text-zero'])
assert.equal(choices[0].warningType,'流量')
assert.equal(rows.length,7)
assert.deepEqual(alarmCurveChoices([]),[])
})
test('reminders request only unhandled alarms and no longer link to ledger',async()=>{
const source=await readFile('src/views/subSystem/alarm/remind.vue','utf8')
assert.match(source,/getAlarmReminders\(\{ \.\.\.scope.value, alarmStatus: 0/)
assert.ok(!source.includes('报警清单'))
assert.ok(!source.includes("navigate('alarmList'"))
assert.ok(source.includes('事件详情'))
})
test('ledger labels are updated and the audit filter is removed without removing its table column',async()=>{
const source=await readFile('src/views/subSystem/alarm/list.vue','utf8')
assert.ok(source.includes('placeholder="请输入设备编码/名称"'))
assert.ok(source.includes(''))
assert.ok(!source.includes(''))
assert.ok(!source.includes('filters.auditStatus'))
assert.ok(!source.includes('auditStatus:'))
assert.ok(source.includes('prop="auditStatusName" label="审核结果"'))
})
test('GIS filters curve options and entries without filtering map or alarm history',async()=>{
const source=await readFile('src/views/subSystem/alarm/drainage/alarmGIS.vue','utf8')
assert.ok(source.includes('alarmCurveChoices([...(selected.value?.alarms || []), ...history.value])'))
assert.ok(source.includes('device.alarms.find(hasActualValue)'))
assert.ok(source.includes('v-if="hasActualValue(row)"'))
assert.ok(source.includes('watch(curveChoices'))
assert.ok(source.includes("tab === 'curve' && currentCurve"))
assert.ok(source.includes('暂无有实际值的报警指标'))
assert.ok(!source.includes('actualValueOnly: true'))
})
test('analysis filters on server before pagination and rejects empty-value deep links',async()=>{
const source=await readFile('src/views/subSystem/alarm/drainage/analysis.vue','utf8')
assert.ok(source.includes('actualValueOnly: true'))
assert.ok(source.includes('alarmCurveChoices(result.data.records || [])'))
assert.ok(source.includes('if (!hasActualValue(result.data))'))
assert.ok(source.indexOf('if (!hasActualValue(result.data))') < source.indexOf('choices.value.unshift(result.data)'))
const detail=await readFile('src/views/subSystem/alarm/AlarmDetail.vue','utf8')
assert.ok(detail.includes("alarm && hasActualValue(alarm) && menu('drainageAlarmAnalysis')"))
})
test('GIS groups events by device and uses highest current grade, ignoring recovered severity',()=>{
const [device] = groupAlarmDevices([
{id:'a',equipmentId:'E1',deviceCode:'D1',createTime:'2026-01-01',alarmLevel:1,alarmStatus:1,longitude:'110',latitude:'28'},
{id:'b',equipmentId:'E1',deviceCode:'D1',createTime:'2026-01-02',alarmLevel:3,alarmStatus:0,longitude:'110',latitude:'28'},
{id:'c',equipmentId:'E1',deviceCode:'D1',createTime:'2026-01-03',alarmLevel:2,alarmStatus:0,longitude:'110',latitude:'28'}
])
assert.equal(device.alarms.length,3); assert.equal(device.alarmLevel,2); assert.equal(device.alarms[0].id,'c'); assert.equal(device.hasCoordinates,true)
})
test('GIS keeps missing or invalid coordinates in device list but does not map them',()=>{
for(const [longitude,latitude] of [[null,28],['',28],[undefined,28],['nan',28],[181,28],[110,91],[0,0]]){
const [device]=groupAlarmDevices([{deviceCode:'D1',longitude,latitude}]); assert.equal(device.hasCoordinates,false)
}
assert.equal(groupAlarmDevices([{deviceCode:'D1',longitude:0,latitude:28}])[0].hasCoordinates,true)
})
test('same leaf route selects only the matching authorized subsystem',()=>{
const routes=[{path:'/water',children:[{path:'alarmList',query:'{"rootTypeId":1}'}]},{path:'/drainage',children:[{path:'alarmList',query:'{"rootTypeId":2}'}]}]
assert.equal(findAlarmMenu(routes,'alarmList',2).path,'/drainage/alarmList')
assert.equal(findAlarmMenu(routes,'alarmList',3),null)
assert.equal(findAlarmMenu([{path:'alarmList',query:'invalid'}],'alarmList',2),null)
})
test('menu navigation reads the real permission store sidebar property',async()=>{
const scope=await readFile('src/views/subSystem/alarm/useAlarmScope.js','utf8')
const store=await readFile('src/store/modules/permission.js','utf8')
const property=scope.match(/findAlarmMenu\(permissions\.(\w+)/)[1]
assert.match(store,new RegExp(property+': \\[\\]'))
})
test('raw series preserve zero and missing values and break large sampling gaps',()=>{
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)
assert.deepEqual(points.map(p=>p[1]),[0,2,null,null,4])
assert.equal(seriesPoints([{time:'2026-01-01 00:00:00',value:0}],true)[0][1],0)
})
test('new GIS entry has the correct name, three tabs and no surrounding environment tab',async()=>{
const source=await readFile('src/views/subSystem/alarm/drainage/alarmGIS.vue','utf8')
assert.ok(source.includes('DrainageAlarmGIS')); assert.ok(!source.includes('周边环境'))
for(const label of ['设备信息','报警记录','监测曲线'])assert.ok(source.includes('label="'+label+'"'))
await assert.rejects(access('src/views/subSystem/alarm/drainage/alarmMap.vue'))
})
test('GIS prototype layout keeps the map width stable with a separate responsive detail drawer',async()=>{
const source=await readFile('src/views/subSystem/alarm/drainage/alarmGIS.vue','utf8')
const styles=await readFile('src/views/subSystem/alarm/drainage/alarmGIS.scss','utf8')
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))
assert.ok(source.includes('{
const source=await readFile('src/views/subSystem/alarm/list.vue','utf8')
assert.match(source,/{
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))
})