import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { lightColors, darkColors, spacing, radius, typography, getCssVariables } from '../src/style/tokens.ts'; import { omniLocales, isRtl, getOmniText } from '../src/locale/omni.ts'; import defaultI18n from '../src/locale/index.ts'; test('1. admin/src/style/tokens.ts - 颜色 Token 校验 (Light & Dark)', () => { // Light 模式颜色 assert.equal(lightColors.primary, '#0B5CFF'); assert.equal(lightColors.primarySoft, '#EAF1FF'); assert.equal(lightColors.ai, '#7C5CFF'); assert.equal(lightColors.bg, '#FFFFFF'); assert.equal(lightColors.surface, '#F7F8FA'); assert.equal(lightColors.border, '#E3E7ED'); assert.equal(lightColors.text, '#101828'); assert.equal(lightColors.text2, '#667085'); assert.equal(lightColors.success, '#12B76A'); assert.equal(lightColors.warning, '#F79009'); assert.equal(lightColors.error, '#F04438'); // Dark 模式颜色 assert.equal(darkColors.primary, '#4D8DFF'); assert.equal(darkColors.primarySoft, '#102A4D'); assert.equal(darkColors.ai, '#9B8CFF'); assert.equal(darkColors.bg, '#0B0D10'); assert.equal(darkColors.surface, '#12151A'); assert.equal(darkColors.border, '#262B33'); assert.equal(darkColors.text, '#F2F4F7'); assert.equal(darkColors.text2, '#98A2B3'); assert.equal(darkColors.success, '#32D583'); assert.equal(darkColors.warning, '#FDB022'); assert.equal(darkColors.error, '#F97066'); }); test('2. admin/src/style/tokens.ts - 间距、圆角与排版校验', () => { // 间距 4, 8, 12, 16, 24, 32, 48, 64 assert.equal(spacing[4], '4px'); assert.equal(spacing[8], '8px'); assert.equal(spacing[12], '12px'); assert.equal(spacing[16], '16px'); assert.equal(spacing[24], '24px'); assert.equal(spacing[32], '32px'); assert.equal(spacing[48], '48px'); assert.equal(spacing[64], '64px'); // 圆角 button 12px, card 16px, modal 20px assert.equal(radius.button, '12px'); assert.equal(radius.card, '16px'); assert.equal(radius.modal, '20px'); // 阴影与排版 assert.equal(typography.shadow, '0 1px 2px rgba(16,24,40,0.04)'); assert.equal(typography.lineHeight.body, 1.6); assert.equal(typography.lineHeight.title, 1.25); assert.equal(typography.lineHeight.arabic, 1.7); }); test('3. admin/src/style/tokens.ts - CSS 变量生成辅助函数', () => { const lightVars = getCssVariables('light'); assert.ok(lightVars['--omni-color-primary'] === '#0B5CFF'); assert.ok(lightVars['--omni-color-bg'] === '#FFFFFF'); assert.ok(lightVars['--omni-radius-card'] === '16px'); const darkVars = getCssVariables('dark'); assert.ok(darkVars['--omni-color-primary'] === '#4D8DFF'); assert.ok(darkVars['--omni-color-bg'] === '#0B0D10'); }); test('4. admin/src/locale/omni.ts - 多语言字典与 RTL 支持', () => { const locales = ['zh-CN', 'en-US', 'ar-SA', 'es-ES', 'de-DE', 'fr-FR', 'ja-JP'] as const; const requiredKeys = [ 'common.confirm', 'common.cancel', 'card.save_contact', 'card.exchange', 'card.ai_chat', 'radar.online_visitor', ] as const; for (const loc of locales) { assert.ok(omniLocales[loc], `应当包含语言包: ${loc}`); for (const key of requiredKeys) { assert.ok(omniLocales[loc].messages[key], `语言 ${loc} 缺少词条 ${key}`); } } // 阿拉伯语 RTL 校验 assert.equal(omniLocales['ar-SA'].dir, 'rtl'); assert.equal(isRtl('ar-SA'), true); assert.equal(omniLocales['zh-CN'].dir, 'ltr'); assert.equal(isRtl('zh-CN'), false); assert.equal(isRtl('en-US'), false); assert.equal(omniLocales['es-ES'].dir, 'ltr'); assert.equal(omniLocales['de-DE'].dir, 'ltr'); assert.equal(omniLocales['fr-FR'].dir, 'ltr'); assert.equal(omniLocales['ja-JP'].dir, 'ltr'); // 获取文案辅助函数 assert.equal(getOmniText('zh-CN', 'card.save_contact'), '保存到通讯录'); assert.equal(getOmniText('en-US', 'card.save_contact'), 'Save Contact'); assert.equal(getOmniText('ar-SA', 'card.save_contact'), 'حفظ جهة الاتصال'); const jaSaveContact = getOmniText('ja-JP', 'card.save_contact'); assert.ok(jaSaveContact.length > 0); assert.notEqual(jaSaveContact, '保存到通讯录'); // 禁止包含任何 emoji 校验 const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u; for (const loc of locales) { for (const [key, val] of Object.entries(omniLocales[loc].messages)) { assert.ok(!emojiRegex.test(val), `语言 ${loc} 的词条 ${key} 严禁含有 emoji: ${val}`); } } }); test('5. mpuniapp/common/tokens.uts - uni-app x 跨端 Token 校验', () => { const utsPath = path.resolve('m:/2026项目/CRMmp/mpuniapp/common/tokens.uts'); assert.ok(fs.existsSync(utsPath), 'tokens.uts 文件必须存在'); const content = fs.readFileSync(utsPath, 'utf-8'); // 必须导出 utsLightColors, utsDarkColors, utsSpacing, utsRadius, utsTypography assert.ok(content.includes('export const utsLightColors: UtsColorTokens =')); assert.ok(content.includes('export const utsDarkColors: UtsColorTokens =')); assert.ok(content.includes('export const utsSpacing: UtsSpacing =')); assert.ok(content.includes('export const utsRadius: UtsRadius =')); assert.ok(content.includes('export const utsTypography: UtsTypography =')); // 校验关键值 (严格遵守 AGENTS.md 权威 Design Token) assert.ok(content.includes("'#0088FF'")); assert.ok(content.includes("'#3894FF'")); assert.ok(content.includes("button: '14px'")); assert.ok(content.includes("card: '20px'")); assert.ok(content.includes("modal: '24px'")); assert.ok(content.includes("space16: '16px'")); }); test('6. admin/src/locale/index.ts - 成功合并 omni 词条且支持 ar-SA', () => { assert.ok(defaultI18n['zh-CN']['card.save_contact'] === '保存到通讯录'); assert.ok(defaultI18n['en-US']['card.save_contact'] === 'Save Contact'); assert.ok(defaultI18n['ar-SA']['card.save_contact'] === 'حفظ جهة الاتصال'); assert.ok(defaultI18n['ar-SA']['radar.online_visitor'] === 'الزوار الحاليون'); assert.equal(defaultI18n['zh-CN']['common.app_name'], 'OmniCard'); });