probe-t64-pages.mjs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. // 探针:真实载荷(GAS 699 条同码)下的分页正确性 —— 逐页读数必须等于 domain 行读数。
  2. import { readFileSync } from 'node:fs'
  3. import { registerHooks } from 'node:module'
  4. import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc'
  5. import { createRenderer, h, ref, nextTick } from '@vue/runtime-core'
  6. const root = new URL('../../../../../src/modules/cockpit/', import.meta.url)
  7. registerHooks({
  8. load(url, context, nextLoad) {
  9. if (url.endsWith('.json')) return { format: 'module', shortCircuit: true, source: `export default ${readFileSync(new URL(url), 'utf8')}` }
  10. if (url.endsWith('.vue')) {
  11. const { descriptor } = parse(readFileSync(new URL(url), 'utf8'), { filename: new URL(url).pathname })
  12. const script = compileScript(descriptor, { id: 'probe', genDefaultAs: '__sfc__' })
  13. const template = compileTemplate({
  14. source: descriptor.template.content, filename: new URL(url).pathname,
  15. id: 'probe', compilerOptions: { bindingMetadata: script.bindings },
  16. })
  17. return { format: 'module', shortCircuit: true, source: `${script.content}\n${template.code}\n__sfc__.render = render; export default __sfc__;` }
  18. }
  19. return nextLoad(url, context)
  20. },
  21. })
  22. const nodeOps = {
  23. createElement: (tag) => ({ tag, props: {}, children: [], parent: null, text: '', style: {}, addEventListener() {}, removeEventListener() {} }),
  24. createText: (text) => ({ tag: '#text', text, children: [], parent: null, props: {}, style: {}, addEventListener() {}, removeEventListener() {} }),
  25. createComment: (text) => ({ tag: '#comment', text, children: [], parent: null, props: {}, style: {}, addEventListener() {}, removeEventListener() {} }),
  26. setText: (n, t) => { n.text = t },
  27. setElementText: (n, t) => { n.text = t; n.children = [] },
  28. insert: (child, parent, anchor) => {
  29. if (child.parent) child.parent.children = child.parent.children.filter((c) => c !== child)
  30. child.parent = parent
  31. if (!parent) return
  32. if (anchor) { const i = parent.children.indexOf(anchor); parent.children.splice(i < 0 ? parent.children.length : i, 0, child) }
  33. else parent.children.push(child)
  34. },
  35. remove: (c) => { if (c.parent) c.parent.children = c.parent.children.filter((x) => x !== c); c.parent = null },
  36. parentNode: (n) => n.parent,
  37. nextSibling: (n) => (n.parent ? n.parent.children[n.parent.children.indexOf(n) + 1] || null : null),
  38. querySelector: () => null,
  39. setScopeId: () => {},
  40. patchProp: (el, k, _p, next) => { el.props[k] = next },
  41. }
  42. const { createApp } = createRenderer(nodeOps)
  43. const { default: PanelBlocks } = await import(new URL('components/PanelBlocks.vue', root))
  44. const { operationPanel, toOperatingInformation } = await import(new URL('domain/infrastructurePanels.js', root))
  45. const { section } = await import(new URL('domain/sectionState.js', root))
  46. function findAll(node, predicate, acc = []) {
  47. if (!node) return acc
  48. if (predicate(node)) acc.push(node)
  49. for (const c of node.children || []) findAll(c, predicate, acc)
  50. return acc
  51. }
  52. function textOf(node) {
  53. if (!node) return ''
  54. // 元素若被 setElementText 直接落文本(Vue 对纯文本元素走这条路),文本在 node.text 上。
  55. const own = node.tag === '#text' ? node.text : (typeof node.text === 'string' ? node.text : '') + (typeof node.props?.text === 'string' ? node.props.text : '')
  56. return own + (node.children || []).map(textOf).join('')
  57. }
  58. const raw = JSON.parse(readFileSync(new URL('../t51/oi-GAS.json', import.meta.url), 'utf8')).data
  59. const data = toOperatingInformation(raw)
  60. const view = operationPanel(data, section('success', { data, loaded: true }), { scope: 'all' })
  61. const rows = view.blocks[1].rows
  62. console.log('row keys distinct:', new Set(rows.map((r) => r.key)).size, '/', rows.length)
  63. const viewRef = ref(view)
  64. const app = createApp({ render: () => h(PanelBlocks, { view: viewRef.value }) })
  65. app.config.warnHandler = () => {}
  66. const container = nodeOps.createElement('#root')
  67. app.mount(container)
  68. function rendered() {
  69. return findAll(container, (n) => n.props && 'data-item' in n.props).map((card) => ({
  70. key: card.props['data-item'],
  71. title: textOf(findAll(card, (n) => n.props && 'class' in n.props && String(n.props['class']).includes('panel-item__title'))[0]),
  72. meta: textOf(findAll(card, (n) => n.props && 'class' in n.props && String(n.props['class']).includes('panel-item__meta'))[0]),
  73. }))
  74. }
  75. let bad = 0
  76. let pagesChecked = 0
  77. for (let page = 1; page <= 70; page += 1) {
  78. const btn = findAll(container, (n) => String(n.props?.['data-page-number']) === String(page))[0]
  79. if (!btn) { console.log('page button missing', page); bad += 1; continue }
  80. btn.props.onClick()
  81. await nextTick()
  82. const got = rendered()
  83. const want = rows.slice((page - 1) * 10, page * 10)
  84. pagesChecked += 1
  85. if (got.length !== want.length) { console.log(`page ${page}: rendered ${got.length} want ${want.length}`); bad += 1; continue }
  86. for (let i = 0; i < want.length; i += 1) {
  87. if (got[i].key !== want[i].key || got[i].meta !== want[i].meta.join(' · ') || got[i].title !== want[i].title) {
  88. console.log(`page ${page} row ${i} mismatch:\n got ${JSON.stringify(got[i])}\n want ${JSON.stringify({ key: want[i].key, title: want[i].title, meta: want[i].meta.join(' · ') })}`)
  89. bad += 1
  90. break
  91. }
  92. }
  93. }
  94. console.log(`pages checked: ${pagesChecked}, mismatches: ${bad}`)
  95. // 计数行 + 到边禁用
  96. const state = findAll(container, (n) => n.props && 'data-page-state' in n.props)[0]
  97. console.log('state on last page:', textOf(state))
  98. const prev = findAll(container, (n) => 'data-page-prev' in (n.props || {}))[0]
  99. const next = findAll(container, (n) => 'data-page-next' in (n.props || {}))[0]
  100. console.log('prev disabled:', prev?.props.disabled, 'next disabled:', next?.props.disabled)