| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- import assert from 'node:assert/strict'
- import { readFileSync } from 'node:fs'
- import vm from 'node:vm'
- import * as Cesium from 'cesium'
- import * as effects from '../../../src/modules/cockpit/domain/deviceEffects.js'
- import { groupOverlappingDevices, fanLayout } from '../../../src/modules/cockpit/domain/overlapDevices.js'
- import { EMPHASIS_STYLE, shouldEmphasize } from '../../../src/modules/cockpit/domain/mapEmphasis.js'
- import { MAP_LABEL_STYLE, mapLabelText } from '../../../src/modules/cockpit/domain/mapLabelStyle.js'
- import { createTilesetLoadingController } from '../../../src/modules/cockpit/services/tilesetLoading.js'
- // Execute the complete production module. Only module imports/CSS are adapted for Node;
- // all engine helpers and Cesium Entity/Property/DataSource implementations remain real.
- const original = readFileSync(new URL('../../../src/modules/cockpit/services/cesiumEngine.js', import.meta.url), 'utf8')
- const mutations = {
- 'owner-only': ["const displayOwner = representsGroup ? (entry || aggregate) : owner", 'const displayOwner = owner'],
- 'wrong-representative': ['overlapEffectCode: representative.code', 'overlapEffectCode: group.rows[0].code'],
- 'fake-visible': ['visible: source.show !== false && entity.show !== false && entity.isShowing !== false', 'visible: true'],
- 'skip-cleanup': ['} else {\n clearRingEntities(source)\n', '} else {\n // mutation: cleanup removed\n'],
- }
- const mutation = process.env.T47_MUTATION
- assert.ok(!mutation || mutations[mutation], 'known mutation')
- const source = mutation ? original.replace(...mutations[mutation]) : original
- if (mutation) assert.notEqual(source, original, 'mutation reached production code')
- const code = source.replace(/^import\s+[^]*?from\s+['"][^'"\n]+['"][ \t]*$/gm, '')
- .replace(/^import\s+['"][^'"\n]+['"][ \t]*$/gm, '')
- .replace(/^export /gm, '')
- const callbacks = new Map()
- let now = 300
- let nextFrame = 0
- const context = vm.createContext({
- ...Cesium, Math, CesiumMath: Cesium.Math, ...effects,
- EMPHASIS_STYLE, shouldEmphasize, MAP_LABEL_STYLE, mapLabelText, createTilesetLoadingController,
- Date: { now: () => now },
- window: {
- requestAnimationFrame(callback) { const id = ++nextFrame; callbacks.set(id, callback); return id },
- cancelAnimationFrame(id) { callbacks.delete(id) },
- },
- })
- vm.runInContext(`${code}\nglobalThis.engine = { syncAssociatedDevices, setOverlapPresentation, applyDeviceEffects, readDeviceEffects, readAssociatedDevices, clearAssociatedDevices, stopDeviceEffects, applyDeviceReactions, pickDeviceMarkerAt };`, context)
- const engine = context.engine
- const viewer = {
- dataSources: new Cesium.DataSourceCollection(),
- scene: { requestRender() {} },
- }
- const rowsOf = (statuses) => statuses.map((status, index) => ({
- code: `EQ-${index}`, pointCode: 'MP-T47', status,
- longitude: 110.3632, latitude: 28.4524, placement: 'drawn',
- coordinate: { renderable: true }, size: 14, shape: status === 'ALARM' ? 'triangle' : 'circle',
- }))
- let rows = rowsOf(['ALARM', 'NORMAL'])
- let group
- let plan
- function load(nextRows = rows, options = {}) {
- rows = nextRows
- const groups = groupOverlappingDevices(rows, { maxFan: options.maxFan || 10 })
- group = groups[0]
- plan = effects.planDeviceEffects({ devices: rows, enabled: options.enabled !== false, abnormalLimit: options.abnormalLimit || 30 })
- engine.syncAssociatedDevices(viewer, rows, () => 'red', () => 'icon', groups)
- engine.applyDeviceEffects(viewer, plan, { devices: rows, fxImageOf: (kind) => kind })
- }
- function frame(time = now) {
- now = time
- const pending = [...callbacks]
- callbacks.clear()
- for (const [, callback] of pending) callback()
- return engine.readDeviceEffects(viewer)
- }
- function present(state, selectedCode = '', preview = false) {
- const fanGroup = selectedCode
- ? { ...group, rows: group.rows.filter((row) => row.code !== selectedCode), maxFan: Math.max(1, group.maxFan - 1) }
- : group
- const fan = selectedCode && group.maxFan <= 1 ? [] : fanLayout(fanGroup)
- const offsets = Object.fromEntries(fan.map((item) => [item.deviceCode, { x: item.x, y: item.y }]))
- engine.setOverlapPresentation(viewer, { groupKey: group.key, state, selectedCode, preview, offsets })
- engine.applyDeviceEffects(viewer, plan, { devices: rows, selectedCode, fxImageOf: (kind) => kind })
- return frame()
- }
- let failed = 0
- let passed = 0
- const check = (name, action) => {
- try { action(); passed += 1; console.log(`PASS ${name}`) }
- catch (error) { failed += 1; console.error(`FAIL ${name}: ${error.message}`) }
- }
- load()
- await Promise.resolve()
- check('collapsed overlap keeps a visible abnormal effect', () => {
- present('collapsed')
- const visible = engine.readAssociatedDevices(viewer).filter((item) => item.id.startsWith('fx|') && item.visible)
- assert.equal(visible.length, 5)
- })
- check('selecting NORMAL preserves group effects without animating the NORMAL device', () => {
- const state = present('selected', 'EQ-1')
- const normal = state.devices.find((item) => item.code === 'EQ-1')
- assert.equal(normal.status, 'NORMAL')
- assert.equal(normal.effect, '')
- assert.equal(normal.alpha, 1)
- assert.ok(state.fx.some((item) => item.visible && item.displayGroup === group.key && item.status === 'ALARM'))
- const entry = engine.readAssociatedDevices(viewer).find((item) => item.deviceCode === 'EQ-1')
- assert.ok(entry.labelText.includes('组内报警'))
- })
- const visibleFx = (state = engine.readDeviceEffects(viewer)) => state.fx.filter((item) => item.visible && item.alpha > 0)
- check('expanded effects follow device offsets and stop claiming group ownership', () => {
- const state = present('expanded')
- const ring = visibleFx(state).find((item) => item.kind === 'ring')
- assert.equal(ring.displayGroup, '')
- const device = engine.readAssociatedDevices(viewer).find((item) => item.deviceCode === ring.for)
- assert.equal(ring.pixelOffset.x, device.pixelOffset.x)
- assert.equal(ring.pixelOffset.y, device.pixelOffset.y)
- })
- check('selected ALARM keeps its device effect and selected boost', () => {
- const normalScale = visibleFx(present('collapsed')).find((item) => item.kind === 'ring' && item.layer === 0).scale
- const selected = visibleFx(present('selected', 'EQ-0'))
- assert.ok(selected.every((item) => item.displayGroup === ''))
- assert.ok(selected.find((item) => item.kind === 'ring' && item.layer === 0).scale > normalScale)
- })
- for (const [status, key] of [['ALARM', 'scale'], ['WARNING', 'alpha'], ['WORK_ORDER', 'rotation']]) {
- for (const staticStatus of ['NORMAL', 'OFFLINE', 'UNKNOWN']) {
- check(`${status} remains visible across frames behind selected ${staticStatus}`, () => {
- load(rowsOf([status, staticStatus]))
- present('selected', 'EQ-1')
- const at300 = visibleFx(frame(300)).find((item) => item.kind === 'ring' && item.layer === 0)
- const at700 = visibleFx(frame(700)).find((item) => item.kind === 'ring' && item.layer === 0)
- assert.ok(at300 && at700)
- assert.notEqual(at300[key], at700[key])
- assert.equal(at700.status, status)
- assert.equal(at700.for, 'EQ-0')
- assert.equal(at700.displayGroup, group.key)
- assert.equal(engine.readDeviceEffects(viewer).devices.find((item) => item.code === 'EQ-1').effect, '')
- })
- }
- }
- check('preview leave returns to a visible group effect without hiding the selected entry', () => {
- load(rowsOf(['ALARM', 'NORMAL']))
- present('expanded', 'EQ-1', true)
- assert.equal(visibleFx().filter((item) => item.displayGroup).length, 0)
- const closed = present('selected', 'EQ-1')
- assert.equal(visibleFx(closed).length, 5)
- assert.equal(engine.readAssociatedDevices(viewer).find((item) => item.deviceCode === 'EQ-1').visible, true)
- })
- check('K11 fallback preserves ALARM even when its device is outside the first ten', () => {
- load(rowsOf([...Array(10).fill('NORMAL'), 'ALARM']))
- assert.equal(group.degraded, true)
- assert.equal(plan.degraded, false)
- const state = present('expanded')
- assert.equal(engine.readAssociatedDevices(viewer).filter((item) => item.deviceCode && item.visible).length, 10)
- assert.equal(visibleFx(state).length, 5)
- assert.ok(visibleFx(state).every((item) => item.displayGroup === group.key && item.for === 'EQ-10'))
- present('expanded', 'EQ-0')
- assert.equal(engine.readAssociatedDevices(viewer).filter((item) => item.deviceCode && item.visible).length, 10)
- assert.equal(visibleFx().length, 5)
- })
- check('group status uses highest priority and does not silently change to selected WARNING', () => {
- load(rowsOf(['WARNING', 'ALARM', 'WORK_ORDER']))
- const state = present('selected', 'EQ-0')
- assert.ok(visibleFx(state).some((item) => item.displayGroup === group.key && item.status === 'ALARM'))
- assert.ok(visibleFx(state).some((item) => !item.displayGroup && item.status === 'WARNING'))
- })
- check('selected peer ALARM does not duplicate the same status at the group entry', () => {
- load(rowsOf(['ALARM', 'ALARM', 'NORMAL']))
- const state = present('selected', 'EQ-1')
- assert.equal(visibleFx(state).length, 5)
- assert.ok(visibleFx(state).every((item) => item.for === 'EQ-1' && item.displayGroup === ''))
- })
- check('only static states create no rings and no animation loop', () => {
- load(rowsOf(['NORMAL', 'OFFLINE', 'UNKNOWN']))
- assert.equal(engine.readDeviceEffects(viewer).fx.length, 0)
- assert.equal(callbacks.size, 0)
- })
- check('30 abnormal devices still animate under overlap degradation', () => {
- load(rowsOf(Array(30).fill('ALARM')))
- assert.equal(plan.degraded, false)
- assert.equal(visibleFx(present('collapsed')).length, 5)
- assert.equal(callbacks.size, 1)
- })
- check('31 abnormal devices stop all rings but retain a static abnormal aggregate and count', () => {
- load(rowsOf(Array(31).fill('ALARM')))
- assert.equal(plan.degraded, true)
- assert.equal(plan.abnormalCount, 31)
- assert.equal(engine.readDeviceEffects(viewer).fx.length, 0)
- assert.equal(callbacks.size, 0)
- const aggregate = engine.readAssociatedDevices(viewer).find((item) => item.overlapAggregate)
- assert.equal(aggregate.visible, true)
- assert.equal(aggregate.deviceStatus, 'ALARM')
- assert.equal(aggregate.overlapCount, 31)
- })
- check('explicit effects off removes rings without removing static device identity', () => {
- load(rowsOf(['ALARM', 'NORMAL']))
- present('selected', 'EQ-1')
- engine.applyDeviceEffects(viewer, effects.planDeviceEffects({ devices: rows, enabled: false }), { devices: rows })
- assert.equal(engine.readDeviceEffects(viewer).fx.length, 0)
- assert.equal(callbacks.size, 0)
- const normal = engine.readAssociatedDevices(viewer).find((item) => item.deviceCode === 'EQ-1')
- assert.equal(normal.visible, true)
- assert.equal(normal.deviceStatus, 'NORMAL')
- })
- check('hidden data source reports no visible rings and restores correctly', () => {
- load(rowsOf(['ALARM', 'NORMAL']))
- const dataSource = viewer.dataSources.getByName('cockpit-associated-device-markers')[0]
- dataSource.show = false
- assert.equal(visibleFx(frame()).length, 0)
- dataSource.show = true
- assert.equal(visibleFx(frame()).length, 5)
- })
- check('stopping effects cancels pending frames and makes retained rings invisible', () => {
- engine.stopDeviceEffects()
- const state = frame()
- assert.equal(visibleFx(state).length, 0)
- assert.equal(callbacks.size, 0)
- })
- check('same device codes with fresh status replace old ALARM rings with WORK_ORDER rings', () => {
- load(rowsOf(['ALARM', 'NORMAL']))
- present('selected', 'EQ-1')
- const previousCount = engine.readDeviceEffects(viewer).fx.length
- load(rowsOf(['WORK_ORDER', 'NORMAL']))
- const state = present('selected', 'EQ-1')
- assert.equal(previousCount, 5)
- assert.equal(state.fx.length, 4)
- assert.ok(visibleFx(state).every((item) => item.status === 'WORK_ORDER'))
- assert.ok(engine.readAssociatedDevices(viewer).find((item) => item.deviceCode === 'EQ-1').labelText.includes('组内工单处置'))
- })
- check('replacement coordinates break overlap and old group ownership does not survive', () => {
- const next = rowsOf(['ALARM', 'NORMAL'])
- next[1].longitude += 0.001
- load(next)
- const state = frame()
- assert.equal(group.k, 1)
- assert.equal(visibleFx(state).length, 5)
- assert.ok(visibleFx(state).every((item) => item.displayGroup === ''))
- })
- check('effect entities cannot be picked as equipment', () => {
- load(rowsOf(['ALARM', 'NORMAL']))
- present('selected', 'EQ-1')
- viewer.canvas = { getBoundingClientRect: () => ({ left: 0, top: 0 }) }
- viewer.clock = { currentTime: Cesium.JulianDate.now() }
- viewer.scene.globe = { ellipsoid: Cesium.Ellipsoid.WGS84, getHeight: () => 0 }
- const target = Cesium.Cartesian3.fromDegrees(110.3632, 28.4524)
- const positionWC = Cesium.Cartesian3.fromDegrees(110.3632, 28.4524, 1000)
- viewer.camera = { positionWC, directionWC: Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(target, positionWC, new Cesium.Cartesian3()), new Cesium.Cartesian3()) }
- context.SceneTransforms = { worldToWindowCoordinates: () => ({ x: 500, y: 300 }) }
- const dataSource = viewer.dataSources.getByName('cockpit-associated-device-markers')[0]
- viewer.scene.pick = () => ({ id: dataSource.entities.values.find((item) => item.properties?.effectFx) })
- assert.equal(engine.pickDeviceMarkerAt(viewer, 700, 500), null)
- const picked = engine.pickDeviceMarkerAt(viewer, 500, 300)
- assert.equal(picked.code, 'EQ-1')
- assert.equal(picked.status, 'NORMAL')
- assert.equal(picked.overlapEntry, true)
- })
- check('clearing the layer removes old effects, owners and scheduled callbacks', () => {
- engine.clearAssociatedDevices(viewer)
- assert.equal(engine.readDeviceEffects(viewer).fx.length, 0)
- assert.equal(engine.readAssociatedDevices(viewer).length, 0)
- assert.equal(callbacks.size, 0)
- const newRows = rowsOf(['WARNING', 'NORMAL']).map((item) => ({ ...item, code: `${item.code}-new`, pointCode: 'MP-NEW' }))
- load(newRows)
- const state = frame()
- assert.ok(state.fx.every((item) => item.for.endsWith('-new')))
- assert.ok(state.devices.every((item) => item.code.endsWith('-new')))
- })
- engine.clearAssociatedDevices(viewer)
- console.log(`T47 overlap/effects: ${passed} passed, ${failed} failed; no browser/GPU verification.`)
- process.exitCode = failed ? 1 : 0
|