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 radarMapPagePath = path.join(adminRoot, 'src/pages/dashboard/radar-map/index.tsx'); const radarMapLocalePath = path.join(adminRoot, 'src/pages/dashboard/radar-map/locale/index.ts'); function flattenRouteKeys(items: typeof routes): string[] { return items.flatMap((item) => [ item.key, ...(item.children ? flattenRouteKeys(item.children) : []), ]); } test('1. routes.ts 必须在 dashboard 分组下注册 dashboard/radar-map', () => { const keys = flattenRouteKeys(routes); assert.ok(keys.includes('dashboard/radar-map'), 'routes.ts 必须包含 dashboard/radar-map 路由'); const dashboardGroup = routes.find((item) => item.key === 'dashboard'); assert.ok(dashboardGroup, '应存在 dashboard 路由分组'); assert.ok( dashboardGroup?.children?.some( (child) => child.key === 'dashboard/radar-map' && child.name === 'menu.dashboard.radarMap' ), 'dashboard 分组下必须包含 menu.dashboard.radarMap (dashboard/radar-map)' ); }); test('2. 主词典 index.ts 必须同步包含 menu.dashboard.radarMap 的中英文案', () => { assert.ok( defaultI18n['en-US']['menu.dashboard.radarMap'], 'en-US 主词典必须包含 menu.dashboard.radarMap' ); assert.ok( defaultI18n['zh-CN']['menu.dashboard.radarMap'], 'zh-CN 主词典必须包含 menu.dashboard.radarMap' ); assert.equal(defaultI18n['zh-CN']['menu.dashboard.radarMap'], '全球访客雷达'); }); test('3. 大屏页面必须存在且使用 Arco Design 官方核心组件', () => { assert.ok(fs.existsSync(radarMapPagePath), 'radar-map/index.tsx 页面文件必须存在'); const content = fs.readFileSync(radarMapPagePath, 'utf-8'); // 组件引入校验 assert.ok(content.includes("from '@arco-design/web-react'"), '必须从 @arco-design/web-react 引入组件'); assert.ok(content.includes('Card'), '必须使用 Arco Card'); assert.ok(content.includes('Grid') || (content.includes('Row') && content.includes('Col')), '必须使用 Arco Grid (Row, Col)'); assert.ok(content.includes('Spin'), '必须使用 Arco Spin 加载反馈'); assert.ok(content.includes('Tag'), '必须使用 Arco Tag 状态与排名标签'); assert.ok(content.includes('Table'), '必须使用 Arco Table 展示国家排行'); assert.ok(content.includes('Progress'), '必须使用 Arco Progress 展示占比进度条'); }); test('4. 大屏页面必须对接雷达聚合分析 API 且只设置 1 个主 CTA', () => { const content = fs.readFileSync(radarMapPagePath, 'utf-8'); // 接口校验 assert.ok( content.includes('/api/admin/crm/radar/analytics') || content.includes('/api/admin/crm/radar/distribution'), '必须对接雷达分析或国家分布 API' ); // 瑞士国际主义与一屏单主 CTA 原则: 统计 type="primary" 按钮数量 const primaryButtonMatches = content.match(/type=["']primary["']/g) || []; assert.equal( primaryButtonMatches.length, 1, '一屏只允许存在 1 个主 CTA 按钮 (type="primary")' ); }); test('5. 必须使用设计 Token 系统且保持严谨纯粹 (零 any、零 emoji)', () => { const content = fs.readFileSync(radarMapPagePath, 'utf-8'); // 设计 Token assert.ok( content.includes('@/style/tokens') || content.includes('tokens'), '必须引用规范设计 Token 系统' ); // 严格 TypeScript,禁止 any assert.ok(!content.includes(': any'), '必须严格定义 TypeScript 类型,严禁使用 : any'); assert.ok(!content.includes('as any'), '必须严格定义 TypeScript 类型,严禁使用 as any'); // 严禁 Emoji 字符 const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u; assert.ok(!emojiRegex.test(content), '页面中严禁硬编码或使用 Emoji 符号'); }); test('6. 页面专属国际化 locale 必须完整且中英文对齐', () => { assert.ok(fs.existsSync(radarMapLocalePath), 'radar-map/locale/index.ts 必须存在'); const localeContent = fs.readFileSync(radarMapLocalePath, 'utf-8'); // 检查关键字典项 assert.ok(localeContent.includes("'en-US'"), '必须包含 en-US'); assert.ok(localeContent.includes("'zh-CN'"), '必须包含 zh-CN'); assert.ok(localeContent.includes('radarMap.title'), '必须包含 radarMap.title'); assert.ok(localeContent.includes('radarMap.topCountries'), '必须包含 radarMap.topCountries'); assert.ok(localeContent.includes('radarMap.deviceAnalytics'), '必须包含 radarMap.deviceAnalytics'); // 国际化文案中严禁 Emoji const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u; assert.ok(!emojiRegex.test(localeContent), '国际化语言包中严禁出现 Emoji 符号'); });