import * as fs from 'fs'; import * as path from 'path'; describe('UI/UX 规范门禁 (TDD 测试套件: 全面杜绝系统 Emoji)', () => { const rootDir = path.resolve(__dirname, '../../'); const uniPagesDir = path.join(rootDir, 'pwuniapp', 'pages'); const docsDir = path.join(rootDir, 'docs'); // 严格 Emoji 正则:匹配所有系统 Emoji、符号表情及特殊 Pictographic 字符 const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u; function getAllFiles(dir: string, extFilter: string[]): string[] { if (!fs.existsSync(dir)) return []; let results: string[] = []; const list = fs.readdirSync(dir); for (const item of list) { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { results = results.concat(getAllFiles(fullPath, extFilter)); } else if (extFilter.some(ext => item.endsWith(ext))) { results.push(fullPath); } } return results; } describe('1. 首页 (index.vue) 杜绝系统 Emoji 校验', () => { it('首页横幅与卡片中不应含有 ⚡ 或任何系统 Emoji', () => { const indexVuePath = path.join(uniPagesDir, 'index', 'index.vue'); const content = fs.readFileSync(indexVuePath, 'utf-8'); // 验证不包含 ⚡ 闪电 Emoji expect(content).not.toContain('⚡'); // 验证不包含裸写的 ♀ / ♂ 字符(防止系统字体回退为彩色系统 Emoji) expect(content).not.toContain('♀'); expect(content).not.toContain('♂'); }); }); describe('2. 前端 pages 目录下所有页面全局无 Emoji 校验', () => { it('所有 .vue 页面组件模板与文本中严禁包含任何 Emoji 字符', () => { const vueFiles = getAllFiles(uniPagesDir, ['.vue']); const violations: { file: string; line: number; text: string }[] = []; for (const file of vueFiles) { const content = fs.readFileSync(file, 'utf-8'); const lines = content.split('\n'); lines.forEach((line, idx) => { if (emojiRegex.test(line)) { violations.push({ file: path.relative(rootDir, file), line: idx + 1, text: line.trim() }); } }); } expect(violations).toEqual([]); }); }); describe('3. UIUX 约束规范文档纯净度校验', () => { it('设计规范文档中严禁使用 ❌ 系统 Emoji', () => { const docFiles = getAllFiles(docsDir, ['.md']); for (const file of docFiles) { const content = fs.readFileSync(file, 'utf-8'); expect(content).not.toContain('❌'); } }); }); describe('4. 首页卡片试听条单行防折行与文本截断规范校验', () => { it('首页试听条容器及文字必须配置单行不折行、弹性伸缩与省略号截断', () => { const indexVuePath = path.join(uniPagesDir, 'index', 'index.vue'); const content = fs.readFileSync(indexVuePath, 'utf-8'); // 1. 验证 .voice-bar 容器样式 const voiceBarMatch = content.match(/\.voice-bar\s*\{([\s\S]*?)\n\s*\.card-footer/); expect(voiceBarMatch).not.toBeNull(); const styleBlock = voiceBarMatch![1]; expect(styleBlock).toContain('box-sizing: border-box'); expect(styleBlock).toContain('white-space: nowrap'); expect(styleBlock).toContain('overflow: hidden'); // 2. 验证 .voice-icon-svg 必须防止被弹性挤压 const iconMatch = styleBlock.match(/\.voice-icon-svg\s*\{([\s\S]*?)\}/); expect(iconMatch).not.toBeNull(); expect(iconMatch![1]).toContain('flex-shrink: 0'); // 3. 验证 .voice-text 核心文本必须具有单行截断三件套与 flex 约束 const textMatch = styleBlock.match(/\.voice-text\s*\{([\s\S]*?)\}/); expect(textMatch).not.toBeNull(); const textStyle = textMatch![1]; expect(textStyle).toContain('flex: 1'); expect(textStyle).toContain('min-width: 0'); expect(textStyle).toContain('white-space: nowrap'); expect(textStyle).toContain('overflow: hidden'); expect(textStyle).toContain('text-overflow: ellipsis'); }); it('mock.js 中试听标题长度必须精简且不得超过12个字,防止在小屏过早被截断', () => { const mockJsPath = path.join(rootDir, 'pwuniapp', 'common', 'mock.js'); const mockContent = fs.readFileSync(mockJsPath, 'utf-8'); const voiceTitleMatches = mockContent.matchAll(/voiceTitle:\s*['"]([^'"]+)['"]/g); for (const match of voiceTitleMatches) { const title = match[1]; expect(title.length).toBeLessThanOrEqual(12); } }); }); describe('5. 活动列表页品类快捷筛选胶囊栏防折行与弹性挤压门禁校验', () => { it('pages/activity/list.vue 中 .capsule-item 与 .capsule-text 必须设置单行防折行与禁止挤压', () => { const rootDir = path.resolve(__dirname, '../../'); const activityVuePath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'list.vue'); expect(fs.existsSync(activityVuePath)).toBe(true); const content = fs.readFileSync(activityVuePath, 'utf-8'); // 验证 .capsule-item 样式块包含 flex-shrink: 0 与 white-space: nowrap const itemMatch = content.match(/\.capsule-item\s*\{([\s\S]*?)\n\s*\.capsule-svg/); expect(itemMatch).not.toBeNull(); const itemStyle = itemMatch![1]; expect(itemStyle).toContain('flex-shrink: 0'); expect(itemStyle).toContain('white-space: nowrap'); // 验证 .capsule-text 文本样式块包含 white-space: nowrap const textMatch = content.match(/\.capsule-text\s*\{([\s\S]*?)\}/); expect(textMatch).not.toBeNull(); const textStyle = textMatch![1]; expect(textStyle).toContain('white-space: nowrap'); }); }); });