| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- import assert from 'node:assert/strict'
- import { readFileSync } from 'node:fs'
- const read = (relative) => readFileSync(new URL(`../../../${relative}`, import.meta.url), 'utf8')
- const engine = read('src/modules/cockpit/services/cesiumEngine.js')
- const mapCanvas = read('src/modules/cockpit/components/MapCanvas.vue')
- const shell = read('src/modules/cockpit/CockpitShell.vue')
- const infoPanel = read('src/modules/cockpit/components/MapInfoPanel.vue')
- const blocked = read('src/modules/cockpit/components/MapCoordinateBlocked.vue')
- const mapService = read('src/modules/cockpit/services/mapService.js')
- // ---- ① 署名容器:必须「自己给一个不进 DOM 的容器」,而不是开关式传参 ----
- assert.match(engine, /creditContainer\s*=\s*document\.createElement\('div'\)/, '必须自建署名容器')
- assert.match(engine, /new Viewer\(host,\s*\{[^}]*creditContainer/, 'creditContainer 必须传给 Viewer')
- const createViewer = engine.slice(engine.indexOf('export function createViewer'))
- const createViewerBody = createViewer.slice(0, createViewer.indexOf('\n}'))
- // 只查**真实语句**(注释里出现 appendChild 这个词不算数)。
- assert.doesNotMatch(createViewerBody, /appendChild\(\s*creditContainer\s*\)/, '署名容器不得进 DOM,否则 Cesium 的默认署名照样上屏')
- assert.doesNotMatch(createViewerBody, /host\.appendChild/, '不得把署名容器挂到宿主上')
- // 传 false / 空串都关不掉(Cesium 两条路径都会建 CreditDisplay),不得回退到这种写法。
- assert.doesNotMatch(engine, /creditContainer:\s*(false|''|"")/, '不得用开关式传参(Cesium 不支持)')
- // ---- ② 合法性前提:不得出现任何 ion 依赖,否则隐藏署名即违约 ----
- const ionSources = ['src/modules/cockpit/services/cesiumEngine.js', 'src/modules/cockpit/services/mapService.js']
- .map(read)
- .join('\n')
- for (const forbidden of [/Cesium\.Ion\b/, /defaultAccessToken/, /createWorldTerrain/, /createWorldImagery/, /assets\.ion\.cesium\.com/, /ArcGisMapServiceImageryProvider/]) {
- assert.doesNotMatch(ionSources, forbidden, `检测到 ion 依赖(${forbidden}),隐藏署名将违反 Cesium 条款`)
- }
- // ---- ③ 数据来源署名必须由我们自己的面板承担 ----
- assert.match(mapService, /export function getDataSourceCredits\(\)/)
- assert.match(mapService, /export function getBasemapLabel\(\)/)
- assert.match(mapService, /dataSourceCredits\s*=\s*\[/, '署名必须从底图/叠加影像/3D Tiles 同一批计划里取')
- assert.match(mapService, /basemapPlan\.credit/, '底图 credit 也要收')
- assert.match(mapService, /overlayPlans\.map\(\(overlay\)\s*=>\s*overlay\.credit\)/, '叠加影像 credit 要收')
- assert.match(infoPanel, /data-map-info-credits/, '面板必须渲染数据来源署名')
- assert.match(infoPanel, /<li v-for="item in credits"/)
- // ---- ④ 地图上只保留顶部状态图例;其余并入**外壳既有状态面板**(不另起容器)----
- assert.match(mapCanvas, /data-map-legend/, '顶部状态筛选图例必须保留(决策 02 的全局入口)')
- for (const gone of ['map-holo__headline', 'map-holo__layers', 'map-holo__bottom', 'map-holo__status', 'map-holo__markers', 'map-holo__degraded']) {
- assert.doesNotMatch(mapCanvas, new RegExp(gone), `${gone} 已并入面板,不应再散挂在地图上`)
- }
- assert.doesNotMatch(mapCanvas, /CockpitIcon|MapCoordinateBlocked|MapInfoPanel/, '这些依赖已随内容块移交外壳')
- // 维护者反馈「容器异常占用」:地图组件不得再自带一个浮层容器。
- assert.doesNotMatch(mapCanvas, /<MapInfoPanel/, '地图组件不再挂载地图信息面板')
- // ---- ④b 并入外壳那一个既有容器(与「数据分区」「状态推送」同框)----
- assert.match(shell, /<MapInfoPanel/, '地图信息必须挂在外壳里')
- assert.match(shell, /class="cockpit-shell__status-details"[\s\S]*?<MapInfoPanel/, '必须落在外壳既有的状态详情容器内(不是另起容器)')
- assert.match(shell, /data-section-board[\s\S]*?<MapInfoPanel/, '紧接共享数据分区之后,与之同框')
- assert.match(shell, /getDataSourceCredits|getBasemapLabel/, '署名与底图措辞由外壳读取')
- // 计数取自同一份模式图层计划,不另开一份真相。
- assert.match(shell, /const mapMarkerCount = computed\(\(\) => modeLayerPlan\.value\.markers\.length\)/)
- // ---- ④c 内容块本身**不定位、不自建开关**(外壳那一个展开/收起已够)----
- assert.doesNotMatch(infoPanel, /position:\s*absolute/, '内容块不得自己定位(会和外壳面板抢容器)')
- assert.doesNotMatch(infoPanel, /right:\s*calc\(var\(--column-w/, '不再自行让开右栏')
- assert.doesNotMatch(infoPanel, /const open = ref\(false\)/, '不再自带展开开关,复用外壳的')
- // ---- ④d 不得截断 ----
- // 只查**真实声明**(注释里提到这些词不算数)。
- assert.doesNotMatch(mapService, /'text-overflow:ellipsis'/, '引擎读数不得再用省略号截断')
- assert.doesNotMatch(mapService, /'white-space:nowrap'/, '引擎读数不得单行截断')
- assert.match(mapService, /'overflow-wrap:anywhere'/, '引擎读数必须换行显示')
- assert.match(infoPanel, /text-overflow:\s*clip\s*!important/, '面板内兜底覆盖引擎节点的内联省略号')
- assert.match(infoPanel, /white-space:\s*normal\s*!important/, '面板内兜底覆盖引擎节点的内联 nowrap')
- // 坐标准入清单:名称与编码都不许省略号截断(维护者反馈编码被从右侧切掉)。
- assert.doesNotMatch(blocked, /text-overflow:\s*ellipsis/, '清单不得截断编码')
- assert.match(blocked, /\.map-blocked__code \{[\s\S]*?overflow-wrap:\s*anywhere/, '编码必须换行显示')
- // ---- ④e 「容器异常占用」与编码被裁已根治 ----
- assert.match(blocked, /\.map-blocked\.map-blocked--embedded[\s\S]*?max-height:\s*none/, '嵌入态必须解除浮块的 max-height')
- assert.match(blocked, /\.map-blocked\.map-blocked--embedded[\s\S]*?align-self:\s*start/, '嵌入态不得被父容器拉高')
- assert.match(blocked, /overflow:\s*visible/, '嵌入态不得再被自身滚动裁掉')
- // 编码被裁的根因:并排时编码列是 `auto` 轨道,会撑到 max-content 顶出容器。
- assert.match(blocked, /\.map-blocked\.map-blocked--embedded \.map-blocked__list li \{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\)/, '嵌入态清单必须单列,长编码不再被裁')
- assert.match(blocked, /\.map-blocked\.map-blocked--embedded \.map-blocked__code \{[\s\S]*?word-break:\s*break-all/, '长编码必须能断行')
- // 图层清单不得静默消失(曾因模板仍引用已删除的 layers prop 而整块不渲染)。
- assert.match(infoPanel, /v-for="layer in LAYERS"/, '图层清单必须渲染')
- assert.doesNotMatch(infoPanel, /v-for="layer in layers"/, '不得引用已移除的 layers prop')
- assert.match(shell, /import MapInfoPanel from/, '外壳必须引入地图信息内容块')
- // ---- ④f 子项宽度必须撑满容器(维护者反馈:展开后清单宽度与容器严重不符)----
- // 根因:`.map-info` 是 grid,`justify-items: start` 会作用到**所有子项**,
- // 让它们按 fit-content 收缩 —— 展开的清单因此比容器窄一大截。
- // 只匹配**真实声明**(注释里提到这个词不算数)。
- assert.doesNotMatch(infoPanel, /^\s*justify-items:\s*start/m, '内容块不得用 justify-items 收窄子项')
- assert.match(infoPanel, /\.map-info > \* \{[\s\S]*?justify-self:\s*stretch/, '子项必须显式撑满容器宽度')
- assert.match(blocked, /\.map-blocked\.map-blocked--embedded[\s\S]*?max-width:\s*100%/, '嵌入块必须允许撑满容器')
- // ---- ⑤ 走查断言面一个都不能丢(面板搬家 ≠ 走查失灵)----
- assert.match(infoPanel, /data-map-marker-count/, '标记计数断言面')
- assert.match(infoPanel, /data-map-degraded-note/, '降级说明断言面')
- assert.match(infoPanel, /<MapCoordinateBlocked[^>]*:embedded="true"/, '坐标准入浮块并入面板且进入嵌入模式')
- assert.match(blocked, /data-map-blocked-panel/, '坐标准入面板断言面')
- assert.match(blocked, /map-blocked--embedded/, '嵌入模式样式类')
- assert.match(blocked, /position:\s*static/, '嵌入后不再是绝对定位浮块')
- // ---- ⑥ 引擎读数靠「搬节点」而不是重算,避免出现第二份真相 ----
- assert.match(mapService, /className = 'map-engine-status'/)
- assert.match(infoPanel, /querySelector\('\.map-engine-status'\)/, '面板提供插槽并搬入引擎节点')
- assert.doesNotMatch(mapService, /createViewer[\s\S]{0,400}creditContainer:\s*new CreditDisplay/, '不得另建一份署名')
- console.log('T25: credit container off-screen, no ion dependency, credits owned by panel, only legend stays on map, walkthrough hooks kept.')
|