import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import vm from 'node:vm' import { resolveDetailAnchor, detailAnchorLocation } from '../../../src/modules/cockpit/domain/detailAnchor.js' import { solvePointAnchor } from '../../../src/modules/cockpit/domain/pointAnchor.js' const root = new URL('../../../src/modules/cockpit/', import.meta.url) const read = (path) => readFileSync(new URL(path, root), 'utf8') const engine = read('services/cesiumEngine.js') const fn = (name) => { const match = engine.match(new RegExp(`(?:export )?function ${name}\\([^]*?\\n\\}`)) assert.ok(match, `${name} exists`) return match[0].replace(/^export /, '') } const property = (value) => ({ getValue: () => value }) const core = { id: 'device|MP-001|EQ-A', show: true, properties: { deviceCode: property('EQ-A') }, billboard: { scale: 1, color: { alpha: 1 }, rotation: 0 }, } core.billboard.pixelOffset = property({ x: 42, y: -26 }) const halo = { id: 'fx|MP-001|EQ-A|ring0', show: true, properties: { effectFx: property(true), effectFxFor: property('EQ-A'), effectFxStatus: property('WARNING'), effectFxKind: property('ring'), effectFxLayer: property(0) }, billboard: { scale: 1, color: { alpha: 1 }, rotation: 0 }, } const source = { entities: { values: [core, halo] } } let renderRequests = 0 const viewer = { scene: { requestRender() { renderRequests += 1 } } } const context = vm.createContext({ deviceEffectState: { viewer, plan: { animated: [{ code: 'EQ-A', effect: 'blink' }] }, selectedCode: '', tick: 0 }, deviceSources: new Map([[viewer, source]]), baseStyleOf: () => ({ scale: 0.75, alpha: 0.45 }), DEVICE_EFFECT: { SPIN: 'spin', BLINK: 'blink' }, Color: { WHITE: { withAlpha: (alpha) => ({ alpha }) } }, blinkStrengthAt: () => 0.25, pulseStrengthAt: () => 0, spinAngleAt: () => 1, glowStrengthAt: () => 0, glowScaleAt: () => 1, ringFrameAt: () => ({ rings: [{ scale: 2, alpha: 0.8 }] }), Date, Cartesian2: class { constructor(x, y) { this.x = x; this.y = y } }, }) vm.runInContext(`${fn('animatedByCode')}\n${fn('writeEffectFrame')}\nwriteEffectFrame()`, context) let failures = 0 const check = (name, action) => { try { action(); console.log(`PASS ${name}`) } catch (error) { failures += 1; console.error(`FAIL ${name}: ${String(error.message).split('\n')[0]}`) } } check('center icon remains opaque at the dimmest blink phase', () => assert.equal(core.billboard.color.alpha, 1)) check('center icon never shrinks below its normal size', () => assert.ok(core.billboard.scale >= 1)) check('blink applies to the outer halo while center stays opaque', () => assert.equal(halo.billboard.color.alpha, 0.2)) check('halo follows the expanded device pixelOffset', () => assert.equal(halo.billboard.pixelOffset, core.billboard.pixelOffset)) check('active effects request a real render', () => assert.ok(renderRequests > 0)) check('halo disappears with its device', () => { core.show = false vm.runInContext('writeEffectFrame()', context) assert.equal(halo.show, false) core.show = true }) const objects = [{ kind: 'monitoring-point', code: 'MP-001', coordinate: { renderable: true } }] const devices = [ { code: 'EQ-A', pointCode: 'MP-001', coordinate: { renderable: true } }, { code: 'EQ-B', pointCode: 'MP-001', coordinate: { renderable: true } }, { code: 'EQ-X', pointCode: 'MP-001', coordinate: { renderable: false, validated: true, reason: 'UNVERIFIED_COORDINATE' } }, ] const target = (level, deviceCode = '') => resolveDetailAnchor({ selection: { level, pointCode: 'MP-001', deviceCode }, objects, devices }) check('equipment A anchors to A while keeping point ownership', () => { assert.equal(target('equipment', 'EQ-A').code, 'EQ-A') assert.equal(target('equipment', 'EQ-A').pointCode, 'MP-001') assert.equal(target('equipment', 'EQ-A').kind, 'monitoring-device') }) check('equipment A to B changes anchor identity', () => assert.equal(target('equipment', 'EQ-B').code, 'EQ-B')) check('back to point summary restores point anchor', () => assert.equal(target('point', 'EQ-B').code, 'MP-001')) check('no-location equipment uses its own verdict', () => assert.equal(detailAnchorLocation(target('equipment', 'EQ-X')).reasonText, '坐标未经校验')) check('missing equipment does not borrow its parent location', () => assert.equal(detailAnchorLocation(target('equipment', 'EQ-missing')).present, false)) check('unassociated equipment anchors to itself', () => { const standalone = { kind: 'unassociated-equipment', code: 'EQ-U' } assert.equal(resolveDetailAnchor({ selection: { level: 'equipment', deviceCode: 'EQ-U' }, objects: [standalone] }).object, standalone) }) check('closed detail has no anchor', () => assert.equal(target('none', 'EQ-A'), null)) check('shell consumes anchor policy and includes line movement in its update key', () => { assert.ok(/resolveDetailAnchor\(/.test(read('CockpitShell.vue'))) assert.ok(/JSON.stringify\(layout.line\)/.test(read('CockpitShell.vue'))) }) // Execute the production projection with a small Cesium public-API double. const entity = { isShowing: true, show: true, position: property({ longitude: 1, latitude: 2 }), properties: { deviceCode: property('EQ-A'), monitoringPointCode: property('MP-001') }, billboard: { pixelOffset: property({ x: 42, y: -26 }), scale: property(1.6), width: property(14), height: property(14) }, } const projectionViewer = { clock: { currentTime: 0 }, canvas: { getBoundingClientRect: () => ({ left: 12, top: 102 }) }, scene: { globe: { ellipsoid: { cartesianToCartographic: (p) => p }, getHeight: () => 123 } }, camera: { positionWC: {}, directionWC: {} }, } function Cartesian3() {} Cartesian3.fromRadians = (longitude, latitude, height) => ({ longitude, latitude, height }) Cartesian3.subtract = () => ({}) Cartesian3.dot = () => 1 const projectionContext = vm.createContext({ Cartesian3, EllipsoidalOccluder: class { isPointVisible() { return true } }, SceneTransforms: { worldToWindowCoordinates: (_scene, position) => { assert.equal(position.height, 123); return { x: 500, y: 320 } } }, deviceSources: new Map([[projectionViewer, { show: true, entities: { values: [entity] } }]]), viewer: projectionViewer, }) vm.runInContext(`${fn('findDeviceMarker')}\n${fn('projectMarkerEntity')}\n${fn('projectDeviceMarker')}`, projectionContext) check('device anchor includes terrain, fan pixelOffset and selected icon size', () => { const at = vm.runInContext("projectDeviceMarker(viewer, 'EQ-A', 'MP-001')", projectionContext) assert.equal(at.x, 554) assert.equal(at.y, 396) assert.equal(at.iconSize, 22.400000000000002) const layout = solvePointAnchor({ anchor: at, canvas: { left: 12, top: 102, right: 1650, bottom: 842 }, iconSize: at.iconSize }) assert.ok(layout.rect.left > at.x + at.iconSize / 2) assert.equal(layout.line.from.x, at.x + at.iconSize / 2) }) check('hidden fan device has no fake parent anchor', () => { entity.show = false assert.equal(vm.runInContext("projectDeviceMarker(viewer, 'EQ-A', 'MP-001')", projectionContext), null) }) check('effects off cancels the loop without hiding the center icon', () => { const cancelled = [] context.window = { cancelAnimationFrame: (id) => cancelled.push(id) } context.clearRingEntities = () => {} context.deviceEffectState.raf = 17 vm.runInContext(`${fn('applyDeviceEffects')}\napplyDeviceEffects(deviceEffectState.viewer, { enabled: false, animated: [] })`, context) assert.deepEqual(cancelled, [17]) assert.equal(context.deviceEffectState.raf, 0) assert.equal(core.billboard.color.alpha, 1) }) check('wrong parent identity never resolves another device marker', () => { entity.show = true assert.equal(vm.runInContext("projectDeviceMarker(viewer, 'EQ-A', 'MP-other')", projectionContext), null) }) check('pending device does not pretend to have a known location failure', () => { assert.equal(detailAnchorLocation({ code: 'EQ-pending', object: { placement: 'pending' } }).present, false) }) console.log(`T47 readability/anchor: ${failures ? 'FAILED' : 'passed'}`) process.exitCode = failures ? 1 : 0