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