Files
danyang-match/tests/public-id-display-coverage.cjs
WorkBuddy 3c2b967274
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Canceled after 0s
CI/CD Pipeline / E2E (live stack) (push) Canceled after 0s
CI/CD Pipeline / Backend Build & Test (push) Canceled after 0s
CI/CD Pipeline / Admin Web Build (push) Canceled after 0s
CI/CD Pipeline / Mini Program Compile Check (push) Canceled after 0s
CI/CD Pipeline / Migration Validation (push) Canceled after 0s
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Canceled after 0s
release: freeze 2026-10-15 validated release baseline
2026-10-15 09:47:31 +08:00

61 lines
3.3 KiB
JavaScript

// shortcut: 静态检查覆盖直接绑定;动态文案和接口映射仍须行为测试与人工验收。
const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const root = path.resolve(__dirname, '..');
const internal = { test: value => (value.match(/\b(?:id|\w*Id)\b/g) || [])
.some(field => !/displayId$/i.test(field)) };
function scan(source) {
const findings = [];
const add = (index, kind, value) => findings.push({
line: source.slice(0, index).split('\n').length, kind, value
});
// 脚本和属性保留行数,但不把路由、事件参数、条件判断当可见文本。
const markup = source.replace(/<script\b[\s\S]*?<\/script>/g, s => s.replace(/[^\n]/g, ' '));
const visible = markup.replace(/<[^>]*>/g, s => s.replace(/[^\n]/g, ' '));
for (const match of visible.matchAll(/\{\{([\s\S]*?)\}\}/g)) {
if (/^\s*(?:categoryLabel\(row\.categoryId\)|orgMap\[row\.organizationId\](?:\s*\|\|\s*'-')?)\s*$/.test(match[1])) continue;
if (internal.test(match[1])) add(match.index, '可见插值需审查', match[1].trim());
}
for (const match of markup.matchAll(/<el-table-column\b[^>]*\bprop=["']([^"']+)["'][^>]*>/g)) {
if (match[1] === 'organizationId' && !match[0].endsWith('/>')) {
const end = markup.indexOf('</el-table-column>', match.index);
if (end >= 0 && /orgMap\[row\.organizationId\]/.test(markup.slice(match.index, end))) continue;
}
if (internal.test(match[1])) add(match.index, '表格字段需审查', match[1]);
}
for (const match of markup.matchAll(/<el-input\b[^>]*\bv-model=["']([^"']+)["'][^>]*>/g)) {
if (internal.test(match[1])) add(match.index, '编号输入需解析', match[1]);
}
return findings;
}
function files(dir) {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
const target = path.join(dir, entry.name);
return entry.isDirectory() ? files(target) : /\.(vue|wxml)$/.test(entry.name) ? [target] : [];
});
}
if (process.argv.includes('--self-check')) {
assert.equal(scan('<text>{{item.userId}}</text>').length, 1);
assert.equal(scan('<text>{{item.displayId}}</text>').length, 0);
assert.equal(scan('<view data-id="{{item.id}}" wx:if="{{item.userId}}">姓名</view>').length, 0);
assert.equal(scan('<el-table-column prop="operatorId" label="红娘" />').length, 1);
assert.equal(scan('<el-input v-model="form.targetUserId" />').length, 1);
assert.equal(scan('<script>const x = "{{item.id}}"</script>').length, 0);
assert.equal(scan('<text>{{orgMap[row.organizationId]}}</text>').length, 0);
assert.equal(scan('<text>{{otherMap[row.userId]}}</text>').length, 1);
assert.equal(scan('<el-table-column prop="organizationId" />').length, 1);
console.log('PASS: 9 项检查器自检(仅允许已审查的名称映射)。');
} else {
const targets = ['admin-web/src', 'miniapp/pages', 'miniapp/components'].flatMap(dir => files(path.join(root, dir)));
let count = 0;
for (const file of targets) {
for (const finding of scan(fs.readFileSync(file, 'utf8'))) {
console.log(`${path.relative(root, file).replaceAll('\\', '/')}:${finding.line} ${finding.kind}: ${finding.value}`);
count++;
}
}
console.log(`${count ? 'FAIL' : 'PASS'}: ${targets.length} 个模板,${count} 个待审查位置。静态结果不代表端到端验收。`);
process.exitCode = count ? 1 : 0;
}