t102-shell-source.mjs 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. /**
  2. * 票 102 闸门的共用取数层(不是独立闸门,由 t102-topbar-labels / t102-topbar-position 引用)。
  3. *
  4. * 为什么要这一份:
  5. * 1. 两个闸门读同一个文件,读法与「导航不许动」的三条契约只该写一遍。
  6. * 2. 断言必须**落在选择器作用域里**。直接对全文件字符串子串断言会出假证据 ——
  7. * 实测 CockpitShell.vue 里 `display: inline-block` 有 2 处、`flex: 0 0 auto` 有 4 处,
  8. * 全文件 includes 就算时钟槽位被删了也照样为绿。
  9. * 3. 计数由运行时的 `check()` 自己累加,不再手写「N/N」——
  10. * 手写数字会和断言条数脱节(上一版就是这么写错成 13/13、14/14 的)。
  11. */
  12. import assert from 'node:assert/strict'
  13. import fs from 'node:fs'
  14. let checks = 0
  15. /** 记一条断言并转交给 node 的 strict assert;计数与断言同一处发生。 */
  16. export function check(condition, message) {
  17. checks++
  18. assert.ok(condition, message)
  19. }
  20. /** 断言「必须为假」的那类,同样计入条数。 */
  21. export function checkAbsent(haystack, needle, message) {
  22. checks++
  23. assert.ok(!haystack.includes(needle), message)
  24. }
  25. /** 收尾打印真实条数。 */
  26. export function report(name) {
  27. console.log(`${name}: ${checks}/${checks}`)
  28. }
  29. /** 读外壳源码;argv[2] 给路径时可复核历史版本(例如提交前的暂存副本必须是红的)。 */
  30. export function readShell(explicitPath) {
  31. return explicitPath
  32. ? fs.readFileSync(explicitPath, 'utf8')
  33. : fs.readFileSync(new URL('../src/modules/cockpit/CockpitShell.vue', import.meta.url), 'utf8')
  34. }
  35. /**
  36. * 取某个类名的规则体(大括号之间)。要求**恰好一处**,否则直接红:
  37. * 出现两处意味着该画法被拆开或重名,断言就不再指向唯一事实源。
  38. */
  39. export function ruleBody(source, className) {
  40. const heads = [...source.matchAll(new RegExp(`\\n\\.${className}\\s*\\{`, 'g'))]
  41. assert.equal(heads.length, 1, `.${className} 规则应为 1 处,实得 ${heads.length} 处`)
  42. const open = heads[0].index + heads[0][0].indexOf('{')
  43. const end = source.indexOf('}', open)
  44. assert.notEqual(end, -1, `.${className} 规则未闭合`)
  45. return source.slice(open + 1, end)
  46. }
  47. /** 只认**声明行**:注释里引用旧写法不该让闸门变红。 */
  48. export function declaration(source, property) {
  49. const m = source.match(new RegExp(`^\\s*${property}:[^;]+;`, 'm'))
  50. assert.ok(m, `没有 ${property} 声明`)
  51. return m[0]
  52. }
  53. /** 一级模式导航的三条不变契约(位置怎么改都不许动语义),两个闸门共用。 */
  54. export function assertModeNavIntact(source) {
  55. check(source.includes('aria-label="一级模式"'), '一级模式导航保持原样')
  56. check(source.includes('v-for="item in MODES"'), '一级模式导航保持原样')
  57. check(source.includes('@click="uiStore.setMode(item)"'), '一级模式导航保持原样')
  58. }