import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import defaultI18n from '../src/locale/index.ts'; const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const profilePagePath = path.join( adminRoot, 'src/pages/crm/lead-profile/index.tsx' ); const profileLocalePath = path.join( adminRoot, 'src/pages/crm/lead-profile/locale/index.ts' ); const emojiPattern = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u; test('1. crm/lead-profile 必须 GET lead/info 并 POST radar/page', () => { assert.ok(fs.existsSync(profilePagePath), '线索画像页面必须存在'); const content = fs.readFileSync(profilePagePath, 'utf-8'); assert.ok( content.includes('admin/crm/lead/info'), '必须请求 GET /api/admin/crm/lead/info' ); assert.ok( content.includes('axios.get'), 'lead/info 必须使用 GET,而不是 POST' ); assert.ok( content.includes('admin/crm/radar/page'), '必须请求 POST /api/admin/crm/radar/page' ); assert.ok(content.includes('axios.post'), '足迹必须使用 POST radar/page'); assert.ok( content.includes('keyWord') && content.includes('visitorId'), '足迹请求必须用 visitorId 作为 keyWord' ); assert.ok(content.includes('cardId'), '足迹请求必须支持 cardId 字段筛选'); assert.ok( content.includes('code === 1000') || content.includes('code ===1000'), '必须按 Cool-Admin code === 1000 判断成功' ); assert.ok(content.includes('Message.error'), '失败必须使用 Arco Message.error'); assert.ok( !content.includes('Mock.js') && !content.includes("from 'mockjs'") && !content.includes('mockjs'), '禁止用 Mock.js 假数据冒充成功' ); assert.ok(!/\bany\b/.test(content), '禁止使用 any'); assert.ok(!emojiPattern.test(content), '禁止使用 emoji'); }); test('2. crm/lead-profile 必须用 Descriptions 或 Card + Table 展示画像与足迹', () => { const content = fs.readFileSync(profilePagePath, 'utf-8'); assert.ok( content.includes("from '@arco-design/web-react'"), '必须从 @arco-design/web-react 引入组件' ); assert.ok(content.includes('Descriptions') || content.includes('Card'), '必须使用 Descriptions 或 Card'); assert.ok(content.includes('Table'), '必须使用 Arco Table'); assert.ok(content.includes('LeadEntity'), '必须使用 LeadEntity 类型'); assert.ok(content.includes('RadarTrack'), '必须使用 RadarTrack 类型'); assert.ok(content.includes("from '@/types/omni'"), '必须复用 admin/src/types/omni.ts'); assert.ok(content.includes("from '@/style/tokens'"), '颜色必须引用设计 token'); for (const field of [ 'name', 'company', 'email', 'phone', 'status', 'intentScore', 'source', 'notes', ]) { assert.ok(content.includes(field), `画像必须展示字段: ${field}`); } for (const field of [ 'touchType', 'stayDurationMs', 'scrollDepth', 'viewedBlocks', 'country', 'city', ]) { assert.ok(content.includes(field), `足迹表必须展示字段: ${field}`); } }); test('3. crm/lead-profile 必须从 URL search 读取 id,无 id 给空状态主 CTA', () => { const content = fs.readFileSync(profilePagePath, 'utf-8'); assert.ok(content.includes('useLocation'), '必须用 react-router useLocation 读路由'); assert.ok(content.includes('location.search'), '必须从 location.search 读取查询串'); assert.ok( content.includes('query-string') || content.includes('queryString') || content.includes('URLSearchParams'), '必须用 querystring 或 URLSearchParams 解析 id' ); assert.ok( content.includes("get('id')") || content.includes('.id') || content.includes("['id']"), '必须读取 query 中的 id' ); assert.ok( content.includes('empty') || content.includes('Empty'), '无 id 必须有空状态' ); assert.ok(content.includes('type="primary"') || content.includes("type='primary'"), '空状态必须有一个主 CTA'); assert.ok(content.includes('/crm/leads'), '空状态主 CTA 应回到线索说明页'); }); test('4. locale 必须包含 menu.crm.leadProfile 中英文案,且禁止 any / mockjs / emoji', () => { assert.ok(fs.existsSync(profileLocalePath), '线索画像语言包必须存在'); const content = fs.readFileSync(profileLocalePath, 'utf-8'); assert.ok( content.includes("'menu.crm.leadProfile'"), '页面语言包必须包含 menu.crm.leadProfile' ); assert.equal(defaultI18n['en-US']['menu.crm.leadProfile'], 'Lead Profile'); assert.equal(defaultI18n['zh-CN']['menu.crm.leadProfile'], '线索画像'); assert.ok(content.includes('crmLeadProfile.request.fail'), '必须包含失败提示文案'); assert.ok(content.includes('crmLeadProfile.empty'), '必须包含空状态文案'); assert.ok(!/\bany\b/.test(content), 'locale 禁止使用 any'); assert.ok( !content.includes('Mock.js') && !content.includes('mockjs'), 'locale 禁止 Mock.js' ); assert.ok(!emojiPattern.test(content), 'locale 禁止使用 emoji'); });