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 articlesUtsPath = path.join(MP_ROOT, 'pages/owner/card/useCardArticles.uts'); const articlesPagePath = path.join(MP_ROOT, 'pages/owner/card/articles.uvue'); /** 禁止预填的示例文章 / 假标题 / 假封面 */ const SAMPLE_BAN_PATTERNS: RegExp[] = [ /2026\s*全球智能硬件合规白皮书/, /全球智能硬件合规白皮书/, /DEMO_ARTICLES/, /SAMPLE_ARTICLES/, /MOCK_ARTICLES/, /placeholder\.png/i, /fake[-_]?cover/i, /unsplash\.com/i, /picsum\.photos/i, ]; test('1. useCardArticles.uts 文件必须存在且格式完整', () => { assert.ok(fs.existsSync(articlesUtsPath), '必须存在 useCardArticles.uts'); const content = fs.readFileSync(articlesUtsPath, 'utf-8'); assert.ok(content.includes('export function createArticlesState'), '必须导出 createArticlesState'); assert.ok(content.includes('export function extractArticleSummary'), '必须导出 extractArticleSummary'); assert.ok(content.includes('export function validateArticleForm'), '必须导出 validateArticleForm'); assert.ok(content.includes('export async function loadArticles'), '必须导出 loadArticles'); assert.ok(content.includes('export async function saveArticle'), '必须导出 saveArticle'); assert.ok(content.includes('export async function toggleArticlePublish'), '必须导出 toggleArticlePublish'); assert.ok(content.includes('export async function deleteArticle'), '必须导出 deleteArticle'); }); test('2. 摘要提取器必须支持剥离 Markdown 与 HTML 语法', () => { const content = fs.readFileSync(articlesUtsPath, 'utf-8'); assert.ok(content.includes('.replace(/<[^>]+>/g, \'\')'), '必须剥离 HTML 标签'); assert.ok(content.includes('.replace(/!\\[.*?\\]\\(.*?\\)/g, \'\')'), '必须剥离 Markdown 图片'); }); test('3. R41: 空列表必须 empty=true,禁止注入示例文章数组', () => { const content = fs.readFileSync(articlesUtsPath, 'utf-8'); assert.ok( content.includes('state.empty = items.length === 0'), 'loadArticles 必须按真实列表长度设置 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_ARTICLES'), '禁止 DEMO_ARTICLES'); assert.ok(!content.includes('SAMPLE_ARTICLES'), '禁止 SAMPLE_ARTICLES'); assert.ok(!content.includes('MOCK_ARTICLES'), '禁止 MOCK_ARTICLES'); }); test('4. R41: 禁止示例标题 / 假封面,新建表单字段为空', () => { const uts = fs.readFileSync(articlesUtsPath, 'utf-8'); const page = fs.readFileSync(articlesPagePath, 'utf-8'); for (const pattern of SAMPLE_BAN_PATTERNS) { assert.ok(!pattern.test(uts), `useCardArticles 禁止示例数据: ${pattern}`); assert.ok(!pattern.test(page), `articles.uvue 禁止示例数据: ${pattern}`); } assert.ok( page.includes("editForm.title = ''") && page.includes("editForm.content = ''"), '新建时 title/content 必须置为空字符串' ); assert.ok( page.includes("editForm.coverImage = ''") || page.includes("uploadedCover.value = []"), '新建时封面必须为空,禁止假封面' ); }); test('5. 文章表单校验逻辑实现规范', () => { const content = fs.readFileSync(articlesUtsPath, 'utf-8'); assert.ok(content.includes("title === ''"), '必须拦截空标题'); assert.ok(content.includes("content === ''"), '必须拦截空正文'); }); test('6. 必须通过 Cool-Admin 标准 CRUD 契约对接后端接口', () => { const content = fs.readFileSync(articlesUtsPath, 'utf-8'); assert.ok(content.includes('fetchCardArticlesPage'), '必须调用 fetchCardArticlesPage'); assert.ok(content.includes('addCardArticle'), '必须调用 addCardArticle'); assert.ok(content.includes('updateCardArticle'), '必须调用 updateCardArticle'); assert.ok(content.includes('deleteCardArticle'), '必须调用 deleteCardArticle'); }); test('7. 国际化与文案键完整覆盖,模板禁止硬编码中文', () => { const uts = fs.readFileSync(articlesUtsPath, 'utf-8'); const page = fs.readFileSync(articlesPagePath, 'utf-8'); assert.ok(uts.includes('resolveArticlesText'), '必须实现文案解析'); assert.ok(uts.includes('Insights & Articles'), '必须支持英文标题'); assert.ok(uts.includes('文章与商务动态'), '必须支持中文标题'); assert.ok(uts.includes('emptyTitle'), '必须含空态标题词条'); assert.ok(uts.includes('emptyDesc'), '必须含空态说明词条'); const templateMatch = page.match(/