import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; const MP_ROOT = path.resolve('m:/2026项目/CRMmp/mpuniapp'); const casesUtsPath = path.join(MP_ROOT, 'pages/owner/card/useCardCases.uts'); const casesPagePath = path.join(MP_ROOT, 'pages/owner/card/cases.uvue'); /** 禁止预填的示例案例 / 客户 / 假 KPI */ const SAMPLE_BAN_PATTERNS: RegExp[] = [ /全球智能硬件数字化交付方案/, /阿联酋国际商业联合会/, /触达\s*10,000\+?\s*买家/, /\+320%\s*ROI/i, /\+150%\s*留资/, /45k\+?\s*国际线索/i, /2M\+?\s*全球曝光/i, /Top\s*1\s*细分类目/i, /VoltRide/i, ]; test('1. useCardCases.uts 文件必须存在且格式完整', () => { assert.ok(fs.existsSync(casesUtsPath), '必须存在 useCardCases.uts'); const content = fs.readFileSync(casesUtsPath, 'utf-8'); assert.ok(content.includes('export function createCasesState'), '必须导出 createCasesState'); assert.ok(content.includes('export function validateCaseForm'), '必须导出 validateCaseForm'); assert.ok(content.includes('export async function loadCases'), '必须导出 loadCases'); assert.ok(content.includes('export async function saveCase'), '必须导出 saveCase'); assert.ok(content.includes('export async function deleteCase'), '必须导出 deleteCase'); assert.ok(content.includes('export function resolveCasesText'), '必须导出 resolveCasesText'); }); test('2. R40: 空列表必须 empty=true,禁止注入示例案例数组', () => { const content = fs.readFileSync(casesUtsPath, 'utf-8'); assert.ok( content.includes('state.empty = items.length === 0'), 'loadCases 必须按真实列表长度设置 empty' ); assert.ok(content.includes('state.list = items'), '列表必须使用接口返回数据'); // 禁止空列表时回退到本地示例数组 assert.ok( !/state\.list\s*=\s*\[[\s\S]*?title\s*:/.test(content), '禁止用内联示例案例数组填充 list' ); assert.ok(!content.includes('DEMO_CASES'), '禁止 DEMO_CASES'); assert.ok(!content.includes('SAMPLE_CASES'), '禁止 SAMPLE_CASES'); assert.ok(!content.includes('MOCK_CASES'), '禁止 MOCK_CASES'); }); test('3. R40: 禁止预设假 KPI / 示例客户,metrics 新建为空', () => { const uts = fs.readFileSync(casesUtsPath, 'utf-8'); const page = fs.readFileSync(casesPagePath, 'utf-8'); assert.ok(!uts.includes('PRESET_CASE_METRICS'), '禁止 PRESET_CASE_METRICS 假 KPI 常量'); assert.ok(!page.includes('PRESET_CASE_METRICS'), '页面禁止引用预设 KPI'); assert.ok(!page.includes('preset-metric-pill'), '禁止预设 KPI 胶囊点击注入'); assert.ok(!page.includes('handleSelectMetric'), '禁止点击注入预设 KPI'); for (const pattern of SAMPLE_BAN_PATTERNS) { assert.ok(!pattern.test(uts), `useCardCases 禁止示例数据: ${pattern}`); assert.ok(!pattern.test(page), `cases.uvue 禁止示例数据: ${pattern}`); } assert.ok( /metrics:\s*['"]{2}/.test(page) || page.includes("editForm.metrics = ''"), '新建时 metrics 必须置为空字符串' ); }); test('4. 案例表单校验逻辑实现规范', () => { const content = fs.readFileSync(casesUtsPath, 'utf-8'); assert.ok(content.includes("title === ''"), '必须拦截空标题'); assert.ok(content.includes('title.length > 150'), '必须拦截超过 150 字符的标题'); }); test('5. 必须通过 Cool-Admin 标准 CRUD 契约对接后端接口', () => { const content = fs.readFileSync(casesUtsPath, 'utf-8'); assert.ok(content.includes('fetchCardCasesPage'), '必须调用 fetchCardCasesPage'); assert.ok(content.includes('addCardCase'), '必须调用 addCardCase'); assert.ok(content.includes('updateCardCase'), '必须调用 updateCardCase'); assert.ok(content.includes('deleteCardCase'), '必须调用 deleteCardCase'); }); test('6. 国际化与文案键完整覆盖,模板禁止硬编码中文', () => { const uts = fs.readFileSync(casesUtsPath, 'utf-8'); const page = fs.readFileSync(casesPagePath, 'utf-8'); assert.ok(uts.includes('resolveCasesText'), '必须实现文案解析'); assert.ok(uts.includes('Portfolio & Case Studies'), '必须支持英文标题'); assert.ok(uts.includes('产品与案例') || uts.includes('产品相册与标杆案例'), '必须支持中文标题'); assert.ok(uts.includes('emptyTitle'), '必须含空态标题词条'); assert.ok(uts.includes('emptyDesc'), '必须含空态说明词条'); // 模板可见文案走 t() / resolveCasesText,禁止 placeholder 硬编码中文示例 const templateMatch = page.match(/