| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- /**
- * T36 主场景区配置 纯逻辑检查(票 36)。
- *
- * 零依赖 node 断言脚本(仓库没配测试运行器,约定见 t01-panel-catalog.mjs)。
- * 运行:node .scratch/cockpit-shell-panel-layout/verify/t36-primary-area.mjs
- *
- * 覆盖验收第 1–7 条里能脱浏览器断言的部分:
- * · 四类输入:合法 / 顺序写反 / 缺字段 / 数值个数不对(验收第 7 条点名要这四类);
- * · **不自动翻转**:把甲方给的 [W, N, E, S] 原样喂进去必须判非法,而不是被悄悄修好;
- * · 关闭路径保留原因:未配置与非法各给得出不同的原因词;
- * · 配置问题清单里确实记了一条(不只是"界面看起来没开");
- * · 本部署显式值与甲方归一化值逐位相同、且与 config.example.json 形状一致;
- * · 静态守卫:`map.primaryArea` 只在配置服务里读、不内置沅陵地理常量、不改前端源码即可生效。
- */
- import assert from 'node:assert/strict'
- import { readFileSync } from 'node:fs'
- import {
- PRIMARY_AREA_LABEL,
- PRIMARY_AREA_OFF_SUFFIX,
- PRIMARY_AREA_REASON,
- PRIMARY_AREA_STATE,
- planPrimaryArea,
- primaryAreaStatusText,
- } from '../../../src/modules/cockpit/domain/primaryArea.js'
- import { builtinConfig, normalizeConfig } from '../../../src/modules/cockpit/services/cockpitConfig.js'
- /** 本部署的显式值(甲方框定范围归一化后:[西, 南, 东, 北];与决策票 01 的契约示例逐位一致)。 */
- const DEPLOY_BOUNDS = [110.328749941417, 28.3917230659234, 110.465779226663, 28.4913038992907]
- /** 甲方原样给的顺序([W, N, E, S])—— 部署期要转换,运行时不转换。 */
- const OWNER_RAW_BOUNDS = [110.32874994141685, 28.491303899290685, 110.46577922666275, 28.3917230659234]
- /** 同上,但用契约示例的精度(第 ⑧ 条那处"关引擎也要读得到"的断言用这一份)。 */
- const SWAPPED_IN_VERIFY = [110.328749941417, 28.4913038992907, 110.465779226663, 28.3917230659234]
- // ---------------------------------------------------------------- 1. 合法
- const ok = planPrimaryArea({ primaryArea: { bounds: DEPLOY_BOUNDS } })
- assert.equal(ok.state, PRIMARY_AREA_STATE.READY, '顺序正确的四元数组应判 ready')
- assert.equal(ok.enabled, true, 'ready 即启用(限位与自建雾可开)')
- assert.deepEqual(ok.bounds, DEPLOY_BOUNDS, '矩形应原样透传')
- assert.equal(ok.reason, '', '启用时不该有原因')
- assert.match(ok.sizeText, /^\d+\.\d×\d+\.\d km$/, `尺寸应是「宽×高 km」,实际 ${ok.sizeText}`)
- assert.match(primaryAreaStatusText(ok), /13\.4×11\.1 km/, '沅陵县城范围约 13.4×11.1 km(浮标读得到尺寸)')
- assert.equal(primaryAreaStatusText(ok).includes(PRIMARY_AREA_OFF_SUFFIX), false, '启用时不得出现「关闭」字样')
- // 边界:零跨度不算合法(west<east、south<north 都是严格小于)
- assert.equal(
- planPrimaryArea({ primaryArea: { bounds: [110, 28, 110, 29] } }).state,
- PRIMARY_AREA_STATE.INVALID,
- '东西零跨度(west === east)应判非法',
- )
- assert.equal(
- planPrimaryArea({ primaryArea: { bounds: [110, 28, 111, 28] } }).state,
- PRIMARY_AREA_STATE.INVALID,
- '南北零跨度(south === north)应判非法',
- )
- // ---------------------------------------------------------------- 2. 顺序写反(不自动翻转)
- const swapped = planPrimaryArea({ primaryArea: { bounds: OWNER_RAW_BOUNDS } })
- assert.equal(swapped.state, PRIMARY_AREA_STATE.INVALID, '甲方给的 [W, N, E, S] 原样喂进来必须判非法')
- assert.equal(swapped.enabled, false, '非法顺序不得启用')
- assert.equal(swapped.bounds, null, '非法时不得给出矩形(不许默默翻转出一个能用的)')
- assert.equal(swapped.reason, PRIMARY_AREA_REASON.ORDER, '原因应点明顺序')
- assert.match(swapped.reason, /不自动翻转/, '原因要说清「运行时不替你翻」')
- const swappedText = primaryAreaStatusText(swapped)
- assert.match(swappedText, /配置无效/, '浮标应说配置无效')
- assert.match(swappedText, /不自动翻转/, '浮标应带顺序口径')
- assert.match(swappedText, new RegExp(PRIMARY_AREA_OFF_SUFFIX), '浮标应说明两个能力一起关了')
- // 只反一半(纬度对、经度反)同样非法:判定是逐轴的,不是"整体看像不像"
- assert.equal(
- planPrimaryArea({ primaryArea: { bounds: [110.465779226663, 28.3917230659234, 110.328749941417, 28.4913038992907] } })
- .state,
- PRIMARY_AREA_STATE.INVALID,
- '经度写反(west > east)也应判非法',
- )
- // ---------------------------------------------------------------- 3. 缺字段
- const missing = planPrimaryArea({})
- assert.equal(missing.state, PRIMARY_AREA_STATE.MISSING, '没有 primaryArea 应判 missing')
- assert.equal(missing.enabled, false, '未配置不得启用')
- assert.equal(missing.reason, PRIMARY_AREA_REASON.MISSING, '原因应是「未配置」')
- const missingText = primaryAreaStatusText(missing)
- assert.match(missingText, /未配置/, '浮标应说未配置')
- assert.match(missingText, new RegExp(PRIMARY_AREA_LABEL), '浮标应点名主场景区')
- assert.equal(missingText.includes(PRIMARY_AREA_OFF_SUFFIX), true, '未配置也必须说明两个能力关了')
- assert.equal(missingText.includes('未配置(未配置)'), false, '不得把同一个词复述两遍(人读的读数要干净)')
- assert.equal(/\((未配置|顺序应为[^)]*)\)\s*$/.test(missingText.replace(` · ${PRIMARY_AREA_OFF_SUFFIX}`, '')), false, '未配置时不该再套一层括号原因')
- // `primaryArea: {}`(写了字段但没填 bounds)由**配置层**判成"写错了"(它知道部署确实写了这个字段);
- // 直接调 domain 时只有 `{}`、没有 `configured`,无从区分"没写"与"写空",如实按未配置处理。
- const emptyObject = planPrimaryArea({ primaryArea: { configured: true } })
- assert.equal(emptyObject.state, PRIMARY_AREA_STATE.INVALID, '给了 primaryArea 却没有 bounds,应判非法而不是未配置')
- assert.equal(planPrimaryArea({ primaryArea: {} }).state, PRIMARY_AREA_STATE.MISSING, '直接调用且没有任何线索时按未配置')
- assert.equal(
- normalizeConfig({ map: { primaryArea: {} } }).config.map.primaryArea.configured,
- true,
- '配置层必须知道「部署写了这个字段」,否则空对象会被读成一句无害的省略',
- )
- // ---------------------------------------------------------------- 4. 数值个数不对
- for (const [label, bad] of [
- ['三个数字', [110, 28, 111]],
- ['五个数字', [110, 28, 111, 29, 30]],
- ['空数组', []],
- ['混入字符串', [110, 28, '111', 29]],
- ['混入 null', [110, 28, null, 29]],
- ['NaN', [110, 28, Number.NaN, 29]],
- ['不是数组', '110,28,111,29'],
- ['对象', { west: 110 }],
- ]) {
- const plan = planPrimaryArea({ primaryArea: { bounds: bad, configured: true } })
- assert.equal(plan.state, PRIMARY_AREA_STATE.INVALID, `${label} 应判非法`)
- assert.equal(plan.reason, PRIMARY_AREA_REASON.SHAPE, `${label} 的原因应是形状问题`)
- assert.equal(plan.bounds, null, `${label} 不得给出矩形`)
- }
- assert.match(primaryAreaStatusText(planPrimaryArea({ primaryArea: { bounds: [1, 2, 3] } })), /四个有限数字/, '形状错应说清要几个数字')
- // ---------------------------------------------------------------- 5. 关闭路径在**配置层**也留痕
- // 三条关闭路径各记一条 issue,且原因词与 domain 那份一致(不是两边各写各的)。
- const missingConfig = normalizeConfig({})
- assert.equal(missingConfig.config.map.primaryArea.configured, false, '没配时内置默认应记成「未配置」')
- assert.deepEqual(missingConfig.config.map.primaryArea.bounds, null, '内置默认不得预置任何地理常量(Q3=A)')
- assert.equal(
- missingConfig.issues.some((item) => item.includes('primaryArea')),
- false,
- '**没配**是合法部署,不该报成配置问题(只由浮标提示)',
- )
- const swappedConfig = normalizeConfig({ map: { primaryArea: { bounds: OWNER_RAW_BOUNDS } } })
- assert.equal(swappedConfig.config.map.primaryArea.configured, true, '给了 bounds 就该记成已配置')
- assert.deepEqual(
- swappedConfig.config.map.primaryArea.bounds,
- OWNER_RAW_BOUNDS,
- '归一化必须原样收下(不翻转)—— 翻转会让配置抄错在运行期隐身',
- )
- assert.equal(swappedConfig.issues.length, 1, `顺序非法应记一条 issue,实际 ${JSON.stringify(swappedConfig.issues)}`)
- assert.match(swappedConfig.issues[0], /primaryArea\.bounds/, 'issue 要点名字段')
- assert.match(swappedConfig.issues[0], /不自动翻转/, 'issue 要带上 domain 那份原因(原因不在链路上丢)')
- assert.match(swappedConfig.issues[0], /限位与雾气关闭/, 'issue 要说清后果')
- const shapeConfig = normalizeConfig({ map: { primaryArea: { bounds: [110, 28, 111] } } })
- assert.equal(shapeConfig.issues.length, 1, '个数不对应记一条 issue')
- assert.match(shapeConfig.issues[0], /四个有限数字/, 'issue 应点明形状要求')
- const notObjectConfig = normalizeConfig({ map: { primaryArea: [110, 28, 111, 29] } })
- assert.equal(notObjectConfig.issues.length, 1, 'primaryArea 不是对象应记一条 issue')
- assert.equal(notObjectConfig.config.map.primaryArea.configured, false, '不是对象时按未配置走关闭路径')
- const okConfig = normalizeConfig({ map: { primaryArea: { bounds: DEPLOY_BOUNDS } } })
- assert.deepEqual(okConfig.issues, [], '合法配置不该报任何问题')
- assert.equal(okConfig.config.map.primaryArea.configured, true, '合法配置应记成已配置')
- assert.deepEqual(okConfig.config.map.primaryArea.bounds, DEPLOY_BOUNDS, '合法配置的矩形应逐位保留')
- // 内置默认本身(不经过任何来源)也要能判成未配置
- assert.equal(planPrimaryArea(builtinConfig().map).state, PRIMARY_AREA_STATE.MISSING, '内置默认 = 未配置')
- // ---------------------------------------------------------------- 6. 本部署显式值 + 模板形状
- // 期望值**从甲方原值现算**(只把纬度的南北两值对调),而不是拿 DEPLOY_BOUNDS 自己比自己。
- // 注意决策票 01 的契约示例把角度写成了 12 位小数(110.32874994141685 → 110.328749941417),
- // 所以这里断言的是「同一个地方」:逐位差 < 1e-9 度(≈0.1 毫米),且**只差截位、不差位置**。
- const expectedNormalized = [OWNER_RAW_BOUNDS[0], OWNER_RAW_BOUNDS[3], OWNER_RAW_BOUNDS[2], OWNER_RAW_BOUNDS[1]]
- const deployFile = JSON.parse(readFileSync(new URL('../../../public/cockpit/config.json', import.meta.url), 'utf8'))
- const deployBounds = deployFile.map.primaryArea.bounds
- assert.equal(deployBounds.length, 4, '本部署配置里的主场景区应是四元数组')
- for (let i = 0; i < 4; i++) {
- assert.equal(
- Math.abs(deployBounds[i] - expectedNormalized[i]) < 1e-9,
- true,
- `第 ${i} 个角应与甲方原值归一化后同一个地方(差 ${deployBounds[i] - expectedNormalized[i]},仅允许截位)`,
- )
- }
- assert.deepEqual(
- [deployBounds[0], deployBounds[2]],
- [110.328749941417, 110.465779226663],
- '经度两值应等于契约示例给出的值(顺序归一化 + 截位,不做别的加工)',
- )
- assert.deepEqual(
- [deployBounds[1], deployBounds[3]],
- [28.3917230659234, 28.4913038992907],
- '纬度两值应等于契约示例给出的值(南 < 北)',
- )
- const exampleFile = JSON.parse(readFileSync(new URL('../../../public/cockpit/config.example.json', import.meta.url), 'utf8'))
- assert.equal(Array.isArray(exampleFile.map.primaryArea.bounds), true, 'config.example.json 要同步该字段形状')
- assert.equal(exampleFile.map.primaryArea.bounds.length, 4, '模板里的主场景区也是四元数组')
- assert.equal(
- planPrimaryArea({ primaryArea: { bounds: exampleFile.map.primaryArea.bounds } }).state,
- PRIMARY_AREA_STATE.READY,
- '模板里的示例值本身必须是合法的(抄模板不该抄出一个非法配置)',
- )
- // ---------------------------------------------------------------- 7. 静态守卫(对抗式)
- const moduleRoot = new URL('../../../src/modules/cockpit/', import.meta.url)
- const readModule = (relative) => readFileSync(new URL(relative, moduleRoot), 'utf8')
- // ① 主场景区只该在「配置服务 + domain 计划」里判,别处不许自己判一遍顺序。
- const areaFiles = [
- 'CockpitShell.vue',
- 'components/MapCanvas.vue',
- 'components/PanelBlocks.vue',
- 'domain/camera.js',
- 'domain/viewModes.js',
- 'domain/viewPresets.js',
- 'services/mapService.js',
- 'services/cameraControl.js',
- 'stores/filterStore.js',
- ].filter((file) => /primaryArea/.test(readModule(file)))
- assert.deepEqual(areaFiles, ['services/mapService.js'], `主场景区只该由 domain 计划 + 地图服务消费,实际:${areaFiles.join(', ')}`)
- // ② 不得内置沅陵/本部署的地理常量(决策票 01 Q3=A:不写死源码,兜底归部署期)。
- const sourceFiles = [
- 'domain/primaryArea.js',
- 'domain/viewPresets.js',
- 'services/cockpitConfig.js',
- 'services/mapService.js',
- ]
- for (const file of sourceFiles) {
- const text = readModule(file)
- for (const literal of ['110.3287499', '110.4657792', '28.3917230', '28.4913038', '110.39028164', '28.4436787']) {
- // viewPresets 的 HOME_DEFAULTS 是票 30 已交付的既有默认,本票不动它;只守主场景区这条链。
- if (file === 'domain/viewPresets.js') continue
- assert.equal(text.includes(literal), false, `${file} 不该内置本部署的地理常量 ${literal}(应由配置给)`)
- }
- }
- // ③ 运行时不翻转:主场景区这条链上不许出现 sort/reverse,也不许对 bounds 的下标做对调。
- // **必须先剥注释再查**:说明里本来就有「不自动翻转」这类字样,直接对全文匹配会被自己的散文判假红
- // (仓库里 F12 被注释里的 `O(n²)` 判红过同一个坑)。
- const stripComments = (text) => text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1')
- /** 抽出所有「由若干 `x[数字]` 组成的数组字面量」的下标序列。 */
- function indexSequences(source) {
- const out = []
- const re = /\[((?:\s*\w+\s*\[\s*\d+\s*\]\s*,?)+)\]/g
- let match
- while ((match = re.exec(source))) {
- out.push([...match[1].matchAll(/\[\s*(\d+)\s*\]/g)].map((item) => Number(item[1])))
- }
- return out
- }
- /** 有没有非升序的下标序列(即"把几个角换了个位置")。 */
- function hasIndexSwap(source) {
- return indexSequences(source).some((seq) => seq.some((value, index) => index > 0 && value < seq[index - 1]))
- }
- const areaCode = stripComments(readModule('domain/primaryArea.js'))
- assert.equal(/\.(sort|reverse)\s*\(/.test(areaCode), false, '主场景区判定不得对 bounds 做排序/翻转(顺序是部署责任)')
- assert.equal(hasIndexSwap(areaCode), false, '判定层不得对 bounds 的下标做对调(那等于替部署翻转)')
- // **守卫有牙**:把一段真会翻转的代码喂进同一个判据,必须认出来。
- // (上一版守卫是全文正则,对本案与对这段样本都判不出东西 —— 变异测试当场证明它无效。)
- assert.equal(hasIndexSwap(stripComments('function f(b){ return [b[0], b[3], b[2], b[1]] }')), true, '守卫要能认出真正的下标对调')
- assert.equal(hasIndexSwap(stripComments('// [b[0], b[3], b[2], b[1]] 这样不行')), false, '守卫不得被注释里的样本判红')
- assert.equal(hasIndexSwap(stripComments('const a = [b[0], b[1], b[2], b[3]]')), false, '升序取值是正常的,不该判红')
- // ④ 只读出口存在:票 37/38 消费的入口必须是"读计划"而不是"猜配置"。
- const serviceSource = readModule('services/mapService.js')
- assert.match(serviceSource, /export function getPrimaryAreaPlan\(/, '地图服务要给出 getPrimaryAreaPlan()(票 37/38 的唯一入口)')
- assert.match(serviceSource, /function applyPrimaryArea\(/, '地图服务要按配置算一次计划')
- assert.match(serviceSource, /dataset\.mapPrimaryArea\b/, '宿主要有 data-map-primary-area 读数(走查与人工排查读它)')
- assert.match(serviceSource, /dataset\.mapPrimaryAreaReason\b/, '宿主要有 data-map-primary-area-reason 保留原因')
- // 引擎被配置关掉时,主场景区读数仍要如实(早退之前就得算)。
- assert.match(
- serviceSource,
- /applyPrimaryArea\(mapConfig\)[\s\S]{0,400}cesiumConfig\.enabled === false/,
- 'applyPrimaryArea 必须在「引擎已关闭」这条早退之前调用,否则关引擎的部署读不到主场景区状态',
- )
- // ⑤ 出口的**行为**(不只存在):未读到配置时如实是未配置,且不得凭空给出矩形;
- // 重置后不得留着上一轮的读数(同进程复跑断言会互相污染)。
- // 这里只做脱浏览器断言 —— 真机走查按 AGENTS.md 由维护者明确要求时才跑。
- const { getPrimaryAreaPlan, getLifecycleStats, resetLifecycleStatsForTest } = await import(
- '../../../src/modules/cockpit/services/mapService.js'
- )
- resetLifecycleStatsForTest()
- const coldPlan = getPrimaryAreaPlan()
- assert.equal(coldPlan.state, PRIMARY_AREA_STATE.MISSING, '还没读到配置时,出口应如实说未配置')
- assert.equal(coldPlan.enabled, false, '还没读到配置时不得启用限位/雾气')
- assert.equal(coldPlan.bounds, null, '还没读到配置时不得凭空给出矩形')
- const coldStats = getLifecycleStats()
- assert.equal(coldStats.primaryArea, PRIMARY_AREA_STATE.MISSING, '生命周期读数应带上主场景区状态')
- assert.equal(coldStats.primaryAreaBounds, null, '未配置时读数里的矩形应为 null')
- // 出口给的是**副本**:消费方改它不得污染服务层那份计划(否则"谁开的、开哪儿"会有第二个来源)。
- const copyProbe = getPrimaryAreaPlan()
- copyProbe.state = 'tampered'
- copyProbe.bounds = [0, 0, 1, 1]
- assert.equal(getPrimaryAreaPlan().state, PRIMARY_AREA_STATE.MISSING, '出口不得被调用方改写(应返回副本)')
- resetLifecycleStatsForTest()
- assert.equal(getPrimaryAreaPlan().bounds, null, '重置后不得残留矩形')
- // ⑥ 关闭路径的措辞必须**区分**"没配"与"配错":两者都关闭,但原因不同,人工排查才不会跑偏。
- assert.notEqual(
- planPrimaryArea({}).reason,
- planPrimaryArea({ primaryArea: { bounds: [1, 2, 3] } }).reason,
- '未配置与形状非法的原因必须不同(否则排查时无从下手)',
- )
- assert.notEqual(
- planPrimaryArea({ primaryArea: { bounds: [1, 2, 3] } }).reason,
- planPrimaryArea({ primaryArea: { bounds: OWNER_RAW_BOUNDS } }).reason,
- '形状非法与顺序非法的原因必须不同',
- )
- // ⑦ 浮标与 dataset 的**真实执行**(脱浏览器):criterion 4 说"人工与走查都能读到",
- // 只靠正则查源码存在不算数 —— 这里用一个最小 DOM 宿主真的把 `paintEngineState` 跑一遍,
- // 读回它写下的 `dataset` 与浮标文本。按 AGENTS.md 不起浏览器/不走 build,这条是等价的执行证据。
- function makeFakeHost() {
- const makeEl = (tag) => ({
- nodeType: 1,
- tagName: String(tag).toUpperCase(),
- dataset: {},
- style: { cssText: '' },
- textContent: '',
- className: '',
- children: [],
- attributes: {},
- setAttribute(name, value) {
- this.attributes[name] = String(value)
- },
- getAttribute(name) {
- return this.attributes[name] === undefined ? null : this.attributes[name]
- },
- appendChild(child) {
- this.children.push(child)
- child.parentNode = this
- return child
- },
- querySelector(selector) {
- // 浮标是唯一的子节点,走查用 [data-map-engine-status] 找它。
- return this.children.find((c) => c.attributes[selector.replace(/[[\]]/g, '')] !== undefined) || null
- },
- remove() {
- if (this.parentNode) this.parentNode.children = this.parentNode.children.filter((c) => c !== this)
- },
- })
- const host = makeEl('div')
- host.ownerDocument = { createElement: (tag) => makeEl(tag) }
- host.querySelectorAll = () => []
- return host
- }
- // mountMap 会同步走 setEngineState(record,'booting') → paintEngineState;引擎那条异步链路
- // 在 unmount 后自行退出(本模块的 `instance !== record` 守卫),所以本条不需要等引擎。
- const { mountMap, unmountMap } = await import('../../../src/modules/cockpit/services/mapService.js')
- resetLifecycleStatsForTest()
- const fakeHost = makeFakeHost()
- mountMap(fakeHost)
- assert.equal(fakeHost.dataset.mapPrimaryArea, PRIMARY_AREA_STATE.MISSING, '宿主 dataset 要真的写上主场景区状态')
- const paintedPill = fakeHost.children[0]
- assert.equal(!!paintedPill, true, '宿主里应真的建出状态浮标节点')
- assert.equal(paintedPill.attributes['data-map-engine-status'], '', '浮标要带 data-map-engine-status(走查找它)')
- // **配置还没读到**时不得宣布任何结论(那时「未配置」只是模块初值,不是核实过的事实)。
- assert.equal(
- paintedPill.textContent.includes('主场景区'),
- false,
- `配置没到手前,浮标不该替部署宣布主场景区的结论,实际「${paintedPill.textContent}」`,
- )
- unmountMap()
- // 让 bootEngine 的异步分支自己退出(它读到 instance 已被销毁就不留实例、不写状态)。
- await new Promise((resolve) => setTimeout(resolve, 30))
- resetLifecycleStatsForTest()
- assert.equal(!!fakeHost.dataset.mapPrimaryArea, true, '重置生命周期计数不该动宿主上已有的读数')
- // ⑧ 关引擎的部署**必须**在浮标上看到主场景区(原生实现里这条是失声的)。
- // 真的走一遍 `bootEngine` 的配置分支:喂一份 `cesium.enabled=false` + 非法顺序的配置,
- // 等它落到 disabled,再读浮标 —— 这道断言就是"DOM 探针抓到真 bug"的那条回归。
- {
- const { resetCockpitConfigForTest, loadCockpitConfig } = await import(
- '../../../src/modules/cockpit/services/cockpitConfig.js'
- )
- resetCockpitConfigForTest()
- await loadCockpitConfig({
- scope: { __COCKPIT_CONFIG__: { map: { cesium: { enabled: false }, primaryArea: { bounds: SWAPPED_IN_VERIFY } } } },
- env: {},
- base: './',
- documentBase: 'http://localhost/',
- })
- resetLifecycleStatsForTest()
- const offHost = makeFakeHost()
- mountMap(offHost)
- // 等 bootEngine 走完配置分支(它要 await 一次 loadCockpitConfig)。
- for (let i = 0; i < 60 && offHost.dataset.mapEngine !== 'disabled'; i++) {
- await new Promise((resolve) => setTimeout(resolve, 25))
- }
- assert.equal(offHost.dataset.mapEngine, 'disabled', 'map.cesium.enabled=false 应让引擎落到 disabled')
- assert.equal(offHost.dataset.mapPrimaryArea, PRIMARY_AREA_STATE.INVALID, '关引擎不影响主场景区的判定')
- assert.equal(
- offHost.dataset.mapPrimaryAreaReason,
- PRIMARY_AREA_REASON.ORDER,
- '关引擎时原因照样要留在 dataset 上',
- )
- const offPill = offHost.children[0]
- assert.match(
- offPill.textContent,
- /主场景区:配置无效(顺序应为 \[西,南,东,北\],不自动翻转) · 限位与雾气关闭/,
- `关引擎的部署也必须在浮标上看到主场景区(原生实现这句是失声的),实际「${offPill.textContent}」`,
- )
- unmountMap()
- await new Promise((resolve) => setTimeout(resolve, 30))
- resetCockpitConfigForTest()
- }
- console.log(
- `T36 检查通过:四类输入(合法 · 顺序写反 · 缺字段 · 个数不对)判定一致 / 不自动翻转(甲方 [W,N,E,S] 原样判非法)/ 三条关闭路径各自留原因 / 配置问题清单记到字段名与后果 / 本部署显式值与甲方原值只差纬度对调 / 模板同步 / 静态守卫(判定唯一、无内置地理常量、无翻转、只读出口齐)。`,
- )
|