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 { routes } from '../src/routes.ts'; import defaultI18n from '../src/locale/index.ts'; const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const radarLogPagePath = path.join(adminRoot, 'src/pages/crm/radar-log/index.tsx'); const radarLogLocalePath = path.join( adminRoot, 'src/pages/crm/radar-log/locale/index.ts' ); function flattenRouteKeys(items: typeof routes): string[] { return items.flatMap((item) => [ item.key, ...(item.children ? flattenRouteKeys(item.children) : []), ]); } test('1. routes.ts 必须包含 key crm/radar-log,供 layout glob 加载', () => { const keys = flattenRouteKeys(routes); assert.ok(keys.includes('crm'), '应存在一级菜单 key: crm'); assert.ok(keys.includes('crm/radar-log'), '应存在路由 key: crm/radar-log'); const crmGroup = routes.find((item) => item.key === 'crm'); assert.ok(crmGroup, '应存在 menu.crm 分组'); assert.equal(crmGroup?.name, 'menu.crm'); assert.ok( crmGroup?.children?.some( (child) => child.key === 'crm/radar-log' && child.name === 'menu.crm.radarLog' ), 'crm 分组下应有 menu.crm.radarLog / crm/radar-log' ); }); test('2. locale 必须包含 menu.crm.radarLog 中英文案', () => { assert.equal(defaultI18n['en-US']['menu.crm.radarLog'], 'Visitor Radar'); assert.equal(defaultI18n['zh-CN']['menu.crm.radarLog'], '访客足迹'); }); test('3. radar-log/index.tsx 必须请求 admin/crm/radar/page 并使用 Arco Table', () => { assert.ok(fs.existsSync(radarLogPagePath), '访客足迹页面必须存在'); const content = fs.readFileSync(radarLogPagePath, 'utf-8'); assert.ok( content.includes('admin/crm/radar/page'), '必须请求 POST /api/admin/crm/radar/page' ); assert.ok(content.includes('Table'), '必须使用 Arco Table'); assert.ok( content.includes("from '@arco-design/web-react'"), '必须从 @arco-design/web-react 引入组件' ); assert.ok(content.includes('Form'), '必须使用 Arco Form'); assert.ok(content.includes('Select'), '必须使用 Arco Select'); assert.ok(content.includes('Tag'), '必须使用 Arco Tag'); 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'"), '禁止用 Mock.js 假数据冒充成功' ); assert.ok(!/\bany\b/.test(content), '禁止使用 any'); }); test('4. radar-log/index.tsx 必须按 RadarTrack / TouchType 渲染足迹列与筛选', () => { const content = fs.readFileSync(radarLogPagePath, 'utf-8'); assert.ok( content.includes('RadarTrack') && content.includes('TouchType'), '必须引用 RadarTrack / TouchType' ); assert.ok(content.includes("from '@/types/omni'"), '必须复用 admin/src/types/omni.ts'); assert.ok(content.includes('touchType'), '必须包含 touchType 筛选与列'); assert.ok(content.includes("'nfc'") && content.includes("'qr'") && content.includes("'link'"), 'touchType 必须覆盖 nfc/qr/link'); assert.ok(content.includes('cardId'), '必须支持 cardId 筛选'); assert.ok(content.includes('country'), '必须支持 country 筛选'); assert.ok(content.includes('keyWord'), '请求体必须包含 keyWord'); assert.ok( content.includes('createTime') && content.includes('createdAt'), '必须兼容 createTime / createdAt' ); assert.ok(content.includes('visitorId'), '必须展示 visitorId'); assert.ok(content.includes('stayDurationMs'), '必须展示 stayDurationMs'); assert.ok(content.includes('scrollDepth'), '必须展示 scrollDepth'); assert.ok(content.includes('viewedBlocks'), '必须展示 viewedBlocks'); assert.ok(content.includes('setData([])'), '失败必须清空表格'); }); test('5. 页面 locale 必须覆盖筛选与列文案,且禁止 any', () => { assert.ok(fs.existsSync(radarLogLocalePath), '访客足迹 locale 必须存在'); const content = fs.readFileSync(radarLogLocalePath, 'utf-8'); assert.ok(content.includes('radarLog.filter.cardId'), '必须有 cardId 筛选文案'); assert.ok(content.includes('radarLog.filter.touchType'), '必须有 touchType 筛选文案'); assert.ok(content.includes('radarLog.filter.country'), '必须有 country 筛选文案'); assert.ok(content.includes('radarLog.columns.viewedBlocks'), '必须有 viewedBlocks 列文案'); assert.ok(!/\bany\b/.test(content), 'locale 禁止使用 any'); });