- 删除无引用的 common/mock.js;登录页与个人中心的演示账号改为 DB 配置 demo_accounts + /api/auth/demo-accounts、/api/auth/demo-login(签发真实 token) - 移除订单/活动/大神/动态/推广/工作台/门票/充值/举报等页面的演示列表与假兜底;新增真实空态 - 修复伪造成功:活动调价、评价回复、核销、海报保存、图片上传失败、提现/核销固定 1001、随机开黑房号、随机即时匹配、假语音播放等 - 服务端移除 101/2006 合成用户与合成大神兜底、移除默认服务包虚构价格;演示账号清单可在后台关闭 - 111 套件/733 用例全绿;已部署 20261021_162041,demo-accounts/demo-login 实测通过
137 行
5.6 KiB
TypeScript
137 行
5.6 KiB
TypeScript
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', () => {
|
||
const mockJsPath = path.join(rootDir, 'pwuniapp', 'common', 'mock.js');
|
||
expect(fs.existsSync(mockJsPath)).toBe(false);
|
||
const apiContent = fs.readFileSync(path.join(rootDir, 'pwuniapp', 'common', 'api.js'), 'utf-8');
|
||
expect(apiContent).not.toContain("common/mock");
|
||
});
|
||
});
|
||
|
||
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');
|
||
});
|
||
});
|
||
});
|
||
|