t56-panel-summary-disclosure.mjs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // 票 56(第二/三轮):所有面板顶部摘要统一折叠,**只有点击**展开/收起。
  2. // 离线闸门:静态断言渲染层 + 用真实注册表做说明字段的**行为层双向完备性普查**。
  3. import fs from 'node:fs'
  4. import assert from 'node:assert/strict'
  5. import { MONITORING_FAULT_GAP, monitoringSummaryPanel } from '../../../src/modules/cockpit/domain/overviewPanels.js'
  6. import { implementedPanelIds, panelView } from '../../../src/modules/cockpit/domain/panelViews.js'
  7. const ROOT = new URL('../../../', import.meta.url)
  8. const panelBlocks = fs.readFileSync(new URL('src/modules/cockpit/components/PanelBlocks.vue', ROOT), 'utf8')
  9. const monitoringPanels = fs.readFileSync(new URL('src/modules/cockpit/domain/monitoringPanels.js', ROOT), 'utf8')
  10. const overviewPanels = fs.readFileSync(new URL('src/modules/cockpit/domain/overviewPanels.js', ROOT), 'utf8')
  11. /**
  12. * 把每个已实现面板的视图真的算一遍,收集「视图级说明字段」(`*Note` / `note` / `gap`)。
  13. * 走注册表而不是正则扫源码:正则会被换缩进/换行写法静默绕过(票 43 的"守卫比要守的宽"教训)。
  14. */
  15. function panelViewRegistryProbe() {
  16. const found = new Set()
  17. const built = []
  18. for (const panelId of implementedPanelIds()) {
  19. let view = null
  20. try {
  21. view = panelView(panelId, {}, null, undefined)
  22. } catch {
  23. view = null
  24. }
  25. if (!view) continue
  26. built.push(panelId)
  27. for (const key of Object.keys(view)) {
  28. if (key === 'gap' || key === 'note' || /Note$/.test(key)) found.add(key)
  29. }
  30. }
  31. return { fields: [...found], built }
  32. }
  33. const registry = panelViewRegistryProbe()
  34. let n = 0
  35. const ok = (cond, msg) => { assert.ok(cond, `A${++n} ${msg}`); console.log(`PASS A${n} ${msg}`) }
  36. // A 组:折叠入口只有点击一条路径
  37. ok(/summaryOpen = ref\(false\)/.test(panelBlocks), '默认折叠(summaryOpen 初值 false)')
  38. ok(/@click="summaryOpen = !summaryOpen"/.test(panelBlocks), '点击是唯一 toggle 路径')
  39. ok(!/summaryHover|summaryFocus|summaryPinned|@mouseenter="summary|@focusin="summary/.test(panelBlocks), 'hover/focus 自动展开已撤销(第二轮维护者裁定)')
  40. ok(/:aria-expanded="summaryOpen"/.test(panelBlocks), 'aria-expanded 绑定同一个状态')
  41. ok(/:aria-controls="summaryDetailsId"/.test(panelBlocks) && /panel-summary-details-\$\{props\.view\.name/.test(panelBlocks), 'aria-controls 按面板名唯一(多面板共存不串 target)')
  42. ok(!/@keydown\.(enter|space)/.test(panelBlocks), '不写显式 keydown:原生 button 的 Enter/Space 已触发 click,重复绑定会双触发互相抵消')
  43. // B 组:摘要条目完备 —— 每条保留原 data-* 断言面
  44. const EXPECTED = [
  45. ['industryNote', 'data-panel-industry'],
  46. ['typeNote', 'data-panel-type-note'],
  47. ['eventNote', 'data-panel-event-note'],
  48. ['rangeNote', 'data-panel-range-note'],
  49. ['gap', 'data-panel-gap'],
  50. ['offlineNote', 'data-panel-offline-note'],
  51. ['pageNote', 'data-panel-page-note'],
  52. ['locateNote', 'data-locate-note'],
  53. ['excludedNote', 'data-panel-excluded-note'],
  54. ['linkNote', 'data-panel-link-note'],
  55. ['note', 'data-panel-note'],
  56. // 票 64:运行信息新增「数据来源与口径」说明。**这条是普查自己抓出来的**——
  57. // 加进 domain 而没加进本表时 A18 当场判红(漏网即隐形),按成例补条目并保留断言面。
  58. ['sourceNote', 'data-panel-source-note'],
  59. ]
  60. for (const [field, hook] of EXPECTED) {
  61. ok(
  62. panelBlocks.includes(`field: '${field}', hook: '${hook}'`),
  63. `摘要条目 ${field} 折叠并保留 ${hook}(走查脚本 t17/t18/t20 选择器不变)`,
  64. )
  65. }
  66. // C 组:**行为层双向完备性普查**(t44 的机制,不靠人记得去查,也不靠正则匹缩进)。
  67. // 用真实注册表算出全部 15 个面板的视图,收集「视图级说明字段」的并集:
  68. // · domain 新增了说明字段而不进折叠表 → 漏网判红(它会在界面上隐形);
  69. // · 折叠表里有字段而任何面板都不再声明 → 死接线判红(无消费点的接线早晚漂移)。
  70. const declared = new Set(registry.fields)
  71. const tabled = new Set(EXPECTED.map(([field]) => field))
  72. const missing = [...declared].filter((f) => !tabled.has(f))
  73. ok(missing.length === 0, `domain 视图声明的说明字段全部进折叠表(漏网:${missing.join(', ') || '无'})`)
  74. const dead = [...tabled].filter((f) => !declared.has(f))
  75. ok(dead.length === 0, `折叠表内字段仍有面板在声明(死接线:${dead.join(', ') || '无'})`)
  76. ok(
  77. ['gap', 'note', 'linkNote', 'excludedNote', 'offlineNote', 'pageNote'].every((f) => declared.has(f)),
  78. `普查真的见到这六类(当前并集 ${declared.size} 条,守卫面没被写窄)`,
  79. )
  80. ok(
  81. registry.built.length >= 12,
  82. `普查覆盖到 12+ 个面板视图(实际 ${registry.built.length} 个:${registry.built.join(', ')})`,
  83. )
  84. // D 组:用户点名的「故障口径」真的落到运行监测面板的摘要里(行为层,不是正则)
  85. {
  86. const view = monitoringSummaryPanel({}, null)
  87. ok(view.gap === MONITORING_FAULT_GAP, '运行监测视图仍由 domain 单源给出故障口径 gap')
  88. ok(view.gap.includes('故障'), '故障口径文案未被动过')
  89. ok(tabled.has('gap'), '故障口径经折叠表的 gap 条目进入摘要(第三轮诉求)')
  90. }
  91. // E 组:旧的独立渲染必须清零(否则折叠形同虚设:同一句话画两遍或仍在头部外裸奔)
  92. ok(!/v-if="view\.(gap|offlineNote|pageNote|rangeNote|industryNote|typeNote|eventNote|locateNote|note|linkNote|excludedNote|sourceNote)\b/.test(panelBlocks), '没有任何说明字段在折叠入口之外独立渲染')
  93. ok(/v-show="summaryOpen"/.test(panelBlocks), '折叠用 v-show:文本仍在 DOM(信息不丢,只改呈现)')
  94. ok(/<header v-if="summaryItems\.length"/.test(panelBlocks), '没有摘要的面板不画空头部')
  95. // F 组:折叠态读数不误导
  96. ok(/有离线设备/.test(panelBlocks) && /当前窗口没有离线设备/.test(panelBlocks), '离线摘要在折叠态仍区分「有离线」与「无离线」')
  97. ok(/view\.gap \|\| view\.note \|\| view\.excludedNote/.test(panelBlocks), '三类口径说明(gap/note/excludedNote)都计入「有口径提示」')
  98. // G 组:domain 数据与措辞零改动(折叠只归渲染层)
  99. ok(/rangeNote: current\.rangeStart && current\.rangeEnd/.test(monitoringPanels), '运行概况统计窗口措辞仍由 domain 单源')
  100. ok(/offlineNote: offlineDevices\.length/.test(monitoringPanels), '离线设备摘要仍由 domain 从异常清单挑选,不推断')
  101. ok(/gap: MONITORING_FAULT_GAP/.test(overviewPanels), '总体概览-运行监测的故障口径缺口仍是 MONITORING_FAULT_GAP 单源')
  102. console.log(`t56 panel summary disclosure: ${n}/${n} passed`)