import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import vm from 'node:vm' import { transitionOverlapState, OVERLAP_STATE, fanLayout, groupOverlappingDevices } from '../../../src/modules/cockpit/domain/overlapDevices.js' import { MAP_LABEL_STYLE, mapLabelText } from '../../../src/modules/cockpit/domain/mapLabelStyle.js' const root = new URL('../../../src/modules/cockpit/', import.meta.url) const engine = readFileSync(new URL('services/cesiumEngine.js', root), 'utf8') const service = readFileSync(new URL('services/mapService.js', root), 'utf8') const extract = (text, name) => text.match(new RegExp(`(?:export )?function ${name}\\([^]*?\\n\\}`))[0].replace(/^export /, '') const prop = (value) => ({ getValue: () => value }) const entities = [ { id: 'aggregate', show: true, properties: { overlapGroup: prop('G'), overlapAggregate: prop(true) }, billboard: { scale: 1, color: { alpha: 1 } }, label: {} }, ...['A', 'B'].map((code) => ({ id: code, show: false, properties: { overlapGroup: prop('G'), deviceCode: prop(code), overlapAggregate: prop(false) }, billboard: { pixelOffset: { x: 0, y: 0 } }, label: {} })), ] const ctx = vm.createContext({ deviceMarkerSource: () => ({ entities: { values: entities } }), Cartesian2: class { constructor(x,y){this.x=x;this.y=y} }, Color: { WHITE: { withAlpha: (alpha) => ({ alpha }) } }, viewer: {} }) vm.runInContext(extract(engine, 'setOverlapPresentation'), ctx) let failed = 0 const check = (name, run) => { try { run(); console.log(`PASS ${name}`) } catch(e) { failed++; console.error(`FAIL ${name}: ${String(e.message).split('\n')[0]}`) } } check('clicking aggregate keeps a dim clickable entry', () => { vm.runInContext("setOverlapPresentation(viewer, {groupKey:'G', state:'expanded', offsets:{A:{x:0,y:-48},B:{x:0,y:48}}})",ctx) assert.equal(entities[0].show,true) assert.ok(entities[0].billboard.color.alpha < 1) }) check('selecting fan device leaves selected device visible at center', () => { vm.runInContext("setOverlapPresentation(viewer, {groupKey:'G', state:'selected', selectedCode:'A'})",ctx) assert.equal(entities[1].show,true) assert.equal(entities[1].billboard.pixelOffset.x,0) assert.equal(entities[2].show,false) }) check('device click transitions to persistent selected state', () => assert.equal(transitionOverlapState('expanded','device-click'), 'selected')) check('aggregate symbol carries readable quantity and separate identity', () => { const ctx2 = vm.createContext({ deviceMarkerSource: () => ({ entities: { removeAll(){}, add(e){ return e } } }), deviceBaseStyle: { clear(){} }, stopDeviceEffects(){}, Cartesian2: class {}, Cartesian3: { fromDegrees(){} }, HeightReference:{}, VerticalOrigin:{}, Color:{WHITE:{},BLACK:{withAlpha:(alpha)=>({alpha})}}, LabelStyle:{}, viewer:{} }) const created = [] ctx2.deviceMarkerSource = () => ({entities:{removeAll(){},add(e){created.push(e);return e}}}) ctx2.MAP_LABEL_STYLE = MAP_LABEL_STYLE ctx2.mapLabelText = mapLabelText ctx2.Color.fromCssColorString = () => ({ withAlpha: (alpha) => ({ alpha }) }) vm.runInContext(extract(engine,'mapTextLabel'),ctx2) vm.runInContext(extract(engine,'syncAssociatedDevices'),ctx2) ctx2.devices = [{code:'A',pointCode:'P',longitude:1,latitude:2,status:'NORMAL'}] ctx2.groups = [{key:'G',pointCode:'P',k:2,rows:ctx2.devices,visual:{status:'NORMAL',shape:'circle'}}] vm.runInContext("syncAssociatedDevices(viewer, devices, ()=>'color', ()=>'image', groups)",ctx2) const aggregateLabel = created.find((e)=>e.id.startsWith('overlap|')).label assert.equal(aggregateLabel.text, '聚合 2 台') assert.equal(aggregateLabel.showBackground, false) assert.equal(created.find((e)=>e.id.startsWith('device|')).label.showBackground, false) assert.equal(aggregateLabel.font, MAP_LABEL_STYLE.count.font) }) check('monitoring point creates a visible name label', () => { const created = [] const pctx=vm.createContext({pointMarkerSource:()=>({entities:{removeAll(){},add(e){created.push(e);return e}}}),markerEntityIndex:new WeakMap(),Cartesian3:{fromDegrees(){}},Cartesian2:class{},HeightReference:{},VerticalOrigin:{},Color:{WHITE:{},BLACK:{withAlpha:(alpha)=>({alpha})}},LabelStyle:{},viewer:{}}) pctx.MAP_LABEL_STYLE = MAP_LABEL_STYLE pctx.mapLabelText = mapLabelText pctx.Color.fromCssColorString = () => ({ withAlpha: (alpha) => ({ alpha }) }) vm.runInContext(extract(engine,'mapTextLabel'),pctx) vm.runInContext(extract(engine,'markerFingerprint'),pctx) vm.runInContext(extract(engine,'syncPointMarkers'),pctx) vm.runInContext("syncPointMarkers(viewer,[{key:'monitoring-point|P',code:'P',kind:'monitoring-point',name:'城南监测点',longitude:1,latitude:2}],()=>'color',()=>'image')",pctx) assert.equal(created[0].label?.text,'⟦ 城南监测点 ⟧') assert.equal(created[0].label.font, MAP_LABEL_STYLE.name.font) assert.equal(created[0].label.showBackground, false) assert.equal(created[0].label.outlineWidth, 2) }) check('device picking does not prewrite service selection before store callback',()=>{ const body=extract(service,'pickDeviceOrPointAt') assert.ok(!body.includes('selectedDeviceCode = resolved.pick.code')) }) // Run the real service selection/hover/click chain against the real engine presentation writer. const mutable = (value) => ({ getValue: () => value, setValue(next) { value = next } }) for (const entity of entities.slice(1)) entity.properties.overlapEntry = mutable(false) let hit = null let selectedByCallback = '' const group = groupOverlappingDevices(['A', 'B'].map((code) => ({ code, pointCode: 'P', longitude: 1, latitude: 2, placement: 'drawn' })))[0] group.key = 'G' const runtime = vm.createContext({ instance: { viewer: {}, engine: { setOverlapPresentation(_viewer, options) { ctx.options = options; vm.runInContext('setOverlapPresentation(viewer, options)', ctx) }, pickDeviceMarkerAt() { return hit }, pickPointMarkerAt() { return null }, pickDeviceMarkerForHover() { return hit }, nudgeOverlap() { return 1 }, } }, overlapGroups: [group], overlapExpandedGroup: '', overlapPreviewGroup: '', overlapState: 'collapsed', overlapCountValue: 0, overlapMaxFan: 10, selectedDeviceCode: '', hoveredDeviceCode: '', lastPicked: null, fanLayout, OVERLAP_STATE, transitionOverlapState, syncDeviceReactions() {}, isDomHost: () => false, paintDeviceReactions() {}, resolveOverlapPick: ({ aggregateHit, deviceHit }) => aggregateHit ? { target: 'overlap', pick: aggregateHit } : deviceHit ? { target: 'device', pick: deviceHit } : { target: 'none' }, pointPickHandlers: new Set([(picked) => { selectedByCallback = picked.code; runtime.wanted = picked.code; vm.runInContext('setSelectedDevice(wanted)', runtime) }]), deviceHoverHandlers: new Set(), }) for (const name of ['applyOverlapPresentation', 'paintOverlapState', 'collapseOverlap', 'resolveOverlapGroupKey', 'nudgeOverlap', 'expandOverlap', 'setSelectedDevice', 'pickDeviceOrPointAt', 'hoverDeviceAt', 'getOverlapState']) { vm.runInContext(extract(service, name), runtime) } const dispatch = (name, target) => { hit = target; return vm.runInContext(`${name}(500,300)`, runtime) } check('hover previews and click fixes the fan without losing the aggregate', () => { dispatch('hoverDeviceAt', { kind: 'monitoring-device-overlap', overlapGroup: 'G' }) assert.equal(entities[0].show, true) assert.equal(entities[1].show, true) assert.equal(entities[0].billboard.scale, 1.2) dispatch('pickDeviceOrPointAt', { kind: 'monitoring-device-overlap', overlapGroup: 'G' }) assert.equal(runtime.overlapExpandedGroup, 'G') assert.equal(entities[0].billboard.color.alpha, 0.35) }) check('fan click goes through callback and leaves A as the visible central entry', () => { dispatch('pickDeviceOrPointAt', { kind: 'monitoring-device', code: 'A', pointCode: 'P', overlapGroup: 'G' }) assert.equal(selectedByCallback, 'A') assert.equal(runtime.selectedDeviceCode, 'A') assert.equal(entities[1].show, true) assert.equal(entities[1].properties.overlapEntry.getValue(), true) assert.equal(entities[2].show, false) assert.equal(entities[1].billboard.pixelOffset.x, 0) assert.equal(entities[1].billboard.pixelOffset.y, 0) }) check('selected entry still previews and click locks peers for switching', () => { const portal = { kind: 'monitoring-device', code: 'A', pointCode: 'P', overlapGroup: 'G', overlapEntry: true } dispatch('hoverDeviceAt', portal) assert.equal(entities[2].show, true) assert.equal(entities[1].billboard.pixelOffset.y, 0) dispatch('pickDeviceOrPointAt', portal) dispatch('hoverDeviceAt', null) assert.equal(entities[2].show, true) assert.equal(runtime.overlapExpandedGroup, 'G') }) check('choosing B transfers center and preserves device identity', () => { dispatch('pickDeviceOrPointAt', { kind: 'monitoring-device', code: 'B', pointCode: 'P', overlapGroup: 'G' }) assert.equal(selectedByCallback, 'B') assert.equal(entities[2].show, true) assert.equal(entities[2].properties.overlapEntry.getValue(), true) assert.equal(entities[1].show, false) assert.equal(entities[1].properties.overlapEntry.getValue(), false) assert.equal(entities[2].billboard.pixelOffset.y, 0) }) check('blank click and preview leave do not hide the selected device', () => { dispatch('hoverDeviceAt', { kind: 'monitoring-device', code: 'B', pointCode: 'P', overlapGroup: 'G', overlapEntry: true }) dispatch('hoverDeviceAt', null) dispatch('pickDeviceOrPointAt', null) assert.equal(entities[2].show, true) assert.equal(vm.runInContext('getOverlapState().state', runtime), 'selected') }) check('selecting from one group clears another active preview', () => { runtime.overlapGroups = [group, { ...group, key: 'H', pointCode: 'Q', rows: [{code:'C',pointCode:'Q'},{code:'D',pointCode:'Q'}] }] runtime.overlapPreviewGroup = 'H' const calls = [] const render = runtime.instance.engine.setOverlapPresentation runtime.instance.engine.setOverlapPresentation = (viewer, options) => { calls.push(options); render(viewer, options) } vm.runInContext("setSelectedDevice('A')", runtime) assert.ok(calls.some((call) => call.groupKey === 'H' && call.state === 'collapsed')) assert.ok(calls.some((call) => call.groupKey === 'G' && call.state === 'selected')) assert.equal(runtime.overlapPreviewGroup, '') runtime.instance.engine.setOverlapPresentation = render }) check('clear device selection restores aggregate and removes selected portal', () => { vm.runInContext("setSelectedDevice('')", runtime) assert.equal(entities[0].show, true) assert.equal(entities[1].show, false) assert.equal(entities[2].show, false) }) check('K11 selected device outside the first ten stays visible with only nine peers', () => { const rows = Array.from({ length: 11 }, (_, index) => ({code:`D${index}`,pointCode:'P',longitude:1,latitude:2,placement:'drawn'})) const large = groupOverlappingDevices(rows)[0] large.key = 'G' runtime.overlapGroups = [large] entities.splice(0, entities.length, {show:true, properties:{overlapGroup:prop('G'),overlapAggregate:prop(true)},billboard:{},label:{}}, ...rows.map((row)=>({show:false,properties:{overlapGroup:prop('G'),overlapAggregate:prop(false),deviceCode:prop(row.code),overlapEntry:mutable(false)},billboard:{},label:{}}))) vm.runInContext("setSelectedDevice('D10'); expandOverlap('G', true)", runtime) assert.equal(entities.filter((entity)=>entity.show && entity.properties.deviceCode).length,10) assert.equal(entities[11].show,true) assert.equal(entities[11].billboard.pixelOffset.x,0) assert.equal(entities[11].billboard.pixelOffset.y,0) assert.equal(vm.runInContext('getOverlapState().count',runtime),11) }) check('expanded device panel clears the fan and stays anchored to its device', () => { runtime.DEVICE_LAYER_KIND = 'monitoring-device' runtime.instance.engine.projectDeviceMarker = () => ({x:500,y:300,iconSize:22}) vm.runInContext(extract(service,'getMapMarkerAnchor'),runtime) const anchor = vm.runInContext("getMapMarkerAnchor('monitoring-device', 'D10', 'P')",runtime) assert.equal(anchor.panelGap,60) assert.equal(anchor.x,500) }) // Actual locate helper must sample device terrain and dispatch its configured tier. const locateBody = service.match(/export async function locateMonitoringPoint[^]*?\n\}/)[0].replace(/^export /,'') const moves = [] const locateContext = vm.createContext({ pointLocateToken:0, instance:{cameraControl:{},viewer:{},engine:{sampleTerrainHeight:async()=>123,groundHeightAt:()=>123}}, isRenderable: (row)=>row.coordinate?.renderable === true, terrainState:'ready',TERRAIN_STATE:{NONE:'none'},isStaleLocate:(a,b)=>a!==b, getCockpitConfig:()=>({map:{}}),planLocateView:(_map,tier)=>({tier}), applyViewPreset:(pivot,hpr)=>{moves.push({pivot,hpr});return true}, setCameraFocus:(pivot)=>{moves.push({pivot});return true}, }) vm.runInContext(locateBody,locateContext) locateContext.device={longitude:1,latitude:2,coordinate:{renderable:true}} await vm.runInContext("locateMonitoringPoint(device,{tier:'device'})",locateContext) check('first device selection dispatches device view at sampled ground height',()=>{ assert.equal(moves[0].hpr.tier,'device') assert.equal(moves[0].pivot.height,123) assert.ok(/void locateMonitoringPoint\(device,/.test(readFileSync(new URL('stores/deviceLayerStore.js',root),'utf8'))) }) await vm.runInContext("locateMonitoringPoint(device,{tier:'device',centerOnly:true})",locateContext) check('device switching only changes center using sampled terrain',()=>{ assert.equal(moves[1].hpr,undefined) assert.equal(moves[1].pivot.height,123) }) const pickedEntity = { show: true, properties: { deviceCode: prop('A'), monitoringPointCode: prop('P'), deviceStatus: prop('NORMAL'), overlapGroup: prop('G'), overlapEntry: prop(true), overlapAggregate: prop(false) }, } const pickContext = vm.createContext({ deviceMarkerSource: () => ({ entities: { values: [pickedEntity] } }), projectMarkerEntity: () => ({ x: 500, y: 300 }), Cartesian2: class { constructor(x, y) { this.x = x; this.y = y } }, viewer: { scene: { pick: () => ({ id: pickedEntity }) }, canvas: { getBoundingClientRect: () => ({ left: 0, top: 0 }) } }, }) for (const name of ['readOverlapTargets', 'overlapTargetAt', 'pipelineSource', 'pickThroughPipelines', 'pickDeviceMarkerAt']) { vm.runInContext(extract(engine, name), pickContext) } check('projected selected-entry pick retains device and group identity', () => { const picked = vm.runInContext('pickDeviceMarkerAt(viewer, 500, 300)', pickContext) assert.equal(picked.code, 'A') assert.equal(picked.pointCode, 'P') assert.equal(picked.overlapGroup, 'G') assert.equal(picked.overlapEntry, true) }) check('ordinary scene pick retains the same selected-entry metadata', () => { const picked = vm.runInContext('pickDeviceMarkerAt(viewer, 700, 500)', pickContext) assert.equal(picked.code, 'A') assert.equal(picked.overlapGroup, 'G') assert.equal(picked.overlapEntry, true) }) console.log(`T45 selection continuity: ${failed ? 'FAILED' : 'passed'}`) process.exitCode=failed ? 1 : 0