294 lines
12 KiB
JavaScript
294 lines
12 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readFile } from 'node:fs/promises';
|
|
import test from 'node:test';
|
|
|
|
const pageSource = async (path) => readFile(new URL(path, import.meta.url), 'utf8');
|
|
|
|
test('全局基础样式不对输入控件设置 border-box', async () => {
|
|
const source = await pageSource('../src/styles/base.css');
|
|
|
|
assert.doesNotMatch(source, /view,\s*\ntext,\s*\ninput,/);
|
|
assert.doesNotMatch(source, /view,\s*\ntext,\s*\ntextarea,/);
|
|
assert.doesNotMatch(source, /(^|\n)input\b[\s\S]*?box-sizing:\s*border-box/m);
|
|
assert.doesNotMatch(source, /(^|\n)textarea\b[\s\S]*?box-sizing:\s*border-box/m);
|
|
});
|
|
|
|
test('SOP 查询框不再覆盖 textarea 的 box-sizing', async () => {
|
|
const source = await pageSource('../src/pages/user/sop/index.vue');
|
|
const queryPanelStyle = source.match(/\.query-panel \.textarea \{[\s\S]*?\n\}/)?.[0] || '';
|
|
|
|
assert.equal(queryPanelStyle.includes('box-sizing'), false);
|
|
});
|
|
|
|
test('员工首页地图主标题保留真实工具名称', async () => {
|
|
const source = await pageSource('../src/pages/user/today/index.vue');
|
|
|
|
assert.match(source, /class="map-symbol"/);
|
|
assert.match(source, /<text class="map-title">\{\{ tile\.title \}\}<\/text>/);
|
|
assert.match(source, /title:\s*'问老师傅'/);
|
|
assert.match(source, /title:\s*'刻意练习'/);
|
|
assert.match(source, /shortTitle:\s*'问'/);
|
|
});
|
|
|
|
test('训练页提交失败会回滚本次乐观追加的对话', async () => {
|
|
const source = await pageSource('../src/pages/user/practice/index.vue');
|
|
|
|
assert.match(source, /const previousTurns = turns\.value\.slice\(\)/);
|
|
assert.match(source, /turns\.value = previousTurns/);
|
|
});
|
|
|
|
test('训练结果和 SOP 答案生成后重置到内容首屏', async () => {
|
|
const [practiceSource, sopSource] = await Promise.all([
|
|
pageSource('../src/pages/user/practice/index.vue'),
|
|
pageSource('../src/pages/user/sop/index.vue')
|
|
]);
|
|
|
|
assert.match(practiceSource, /result\.value = await finishPractice\([\s\S]*?resetPageScroll\(\)/);
|
|
assert.match(sopSource, /result\.value = await searchKnowledge\(value\);\s*resetPageScroll\(\)/);
|
|
});
|
|
|
|
test('训练历史按摘要、逐句复盘、SOP 依据和行动建议分层', async () => {
|
|
const source = await pageSource('../src/pages/user/history/index.vue');
|
|
|
|
assert.match(source, /训练摘要/);
|
|
assert.match(source, /录音与逐句复盘/);
|
|
assert.match(source, /SOP 依据与改进建议/);
|
|
assert.match(source, /推荐话术/);
|
|
});
|
|
|
|
test('案例页为无权限和无案例提供明确空状态', async () => {
|
|
const source = await pageSource('../src/pages/user/cases/index.vue');
|
|
|
|
assert.match(source, /class="case-empty-state"/);
|
|
assert.match(source, /暂时还没有可学习案例/);
|
|
assert.match(source, /提交权限由组织角色决定/);
|
|
});
|
|
|
|
test('manifest 明确声明为 uni-app Vue3 CLI 工程', async () => {
|
|
const source = await pageSource('../src/manifest.json');
|
|
|
|
assert.match(source, /"vueVersion"\s*:\s*"3"/);
|
|
});
|
|
|
|
test('员工端 tabBar 继续保留四个固定入口', async () => {
|
|
const source = await pageSource('../src/pages.json');
|
|
|
|
assert.match(source, /"selectedColor"\s*:\s*"#e60012"/);
|
|
assert.match(source, /pages\/user\/today\/index/);
|
|
assert.match(source, /pages\/user\/practice\/index/);
|
|
assert.match(source, /pages\/user\/sop\/index/);
|
|
assert.match(source, /pages\/user\/profile\/index/);
|
|
});
|
|
|
|
test('登录页按高保真原型保留品牌结构和紧凑验证码行', async () => {
|
|
const source = await pageSource('../src/pages/auth/login/index.vue');
|
|
|
|
assert.match(source, /高效协同 · 贴心服务 · 让工作更简单/);
|
|
assert.match(source, /<uni-icons[^>]+type="phone"/);
|
|
assert.match(source, /<uni-icons[^>]+type="locked"/);
|
|
assert.doesNotMatch(source, /class="logo-badge"/);
|
|
assert.doesNotMatch(source, /grid-template-columns:\s*1fr;[\s\S]*?\.code-button/);
|
|
});
|
|
|
|
test('共享视觉基座包含员工端主色和卡片圆角 token', async () => {
|
|
const source = await pageSource('../src/styles/base.css');
|
|
|
|
assert.match(source, /lxgw-wenkai-tc-webfont/);
|
|
assert.match(source, /font-family:\s*"LXGW WenKai TC"/);
|
|
assert.match(source, /--employee-primary:\s*#e60012/);
|
|
assert.match(source, /--employee-card-radius:\s*24px/);
|
|
assert.match(source, /\.employee-card\s*\{/);
|
|
});
|
|
|
|
test('员工端三页接入共享页面和卡片基座', async () => {
|
|
const [todaySource, sopSource, practiceSource] = await Promise.all([
|
|
pageSource('../src/pages/user/today/index.vue'),
|
|
pageSource('../src/pages/user/sop/index.vue'),
|
|
pageSource('../src/pages/user/practice/index.vue')
|
|
]);
|
|
|
|
assert.match(todaySource, /employee-page-shell/);
|
|
assert.match(todaySource, /section-card employee-card/);
|
|
assert.match(sopSource, /query-panel employee-card/);
|
|
assert.match(practiceSource, /scenario-card employee-card/);
|
|
});
|
|
|
|
test('登录后按组织身份切换主管首页', async () => {
|
|
const source = await pageSource('../src/services/position.ts');
|
|
|
|
assert.match(source, /\/api\/aihr\/mobile\/me/);
|
|
assert.match(source, /role === 'supervisor'/);
|
|
assert.match(source, /pages\/supervisor\/index\/index/);
|
|
assert.match(source, /removeStorageSync\(roleKey\)/);
|
|
});
|
|
|
|
test('普通员工请求主管深链时回到员工首页', async () => {
|
|
const source = await pageSource('../src/services/position.ts');
|
|
|
|
assert.match(source, /role === 'user' && isSupervisorRoute\(requested\)/);
|
|
assert.match(source, /return '\/pages\/user\/today\/index'/);
|
|
});
|
|
|
|
test('员工首页保留师傅区、语音条、今日安排、我的地图四段结构', async () => {
|
|
const source = await pageSource('../src/pages/user/today/index.vue');
|
|
|
|
assert.match(source, /mentor-hero/);
|
|
assert.match(source, /voice-panel/);
|
|
assert.match(source, /今日安排/);
|
|
assert.match(source, /我的地图/);
|
|
assert.match(source, /brand-bar/);
|
|
assert.match(source, /ask-card/);
|
|
assert.match(source, /小贴士/);
|
|
});
|
|
|
|
test('员工首页高保真入口继续使用真实图标和业务跳转', async () => {
|
|
const source = await pageSource('../src/pages/user/today/index.vue');
|
|
|
|
assert.match(source, /type="notification"/);
|
|
assert.match(source, /type="chatboxes"/);
|
|
assert.match(source, /type="compose"/);
|
|
assert.match(source, /type="location"/);
|
|
assert.match(source, /const goSop = \(\) =>/);
|
|
assert.match(source, /const openPrimarySchedule = \(\) =>/);
|
|
assert.match(source, /getMobileHome\('user'\)/);
|
|
assert.match(source, /getPracticeAssignments\(true\)/);
|
|
assert.match(source, /src="\/static\/mentor-hifi\.png"/);
|
|
});
|
|
|
|
test('员工首页今日安排保留派发任务来源', async () => {
|
|
const source = await pageSource('../src/pages/user/today/index.vue');
|
|
|
|
assert.match(source, /primaryAssignmentSourceLabel/);
|
|
assert.match(source, /source === 'retry'[\s\S]*错题再练/);
|
|
assert.match(source, /source === 'camp'[\s\S]*训练营/);
|
|
assert.match(source, /class="schedule-source"/);
|
|
});
|
|
|
|
test('问师傅页保留搜索框、答案区和总结卡入口', async () => {
|
|
const source = await pageSource('../src/pages/user/sop/index.vue');
|
|
|
|
assert.match(source, /query-panel/);
|
|
assert.match(source, /answer-panel/);
|
|
assert.match(source, /summary-panel/);
|
|
});
|
|
|
|
test('SOP 没解决先收集结构化原因再提交', async () => {
|
|
const [source, service] = await Promise.all([
|
|
pageSource('../src/pages/user/sop/index.vue'),
|
|
pageSource('../src/services/knowledge.ts')
|
|
]);
|
|
|
|
assert.match(source, /feedbackReasonOptions/);
|
|
assert.match(source, /negative-feedback-panel/);
|
|
assert.match(source, /submitNegativeFeedback/);
|
|
assert.match(source, /reasonCodes:\s*selectedFeedbackReasons\.value/);
|
|
assert.match(source, /comment:\s*feedbackComment\.value\.trim\(\)/);
|
|
assert.match(service, /reasonCodes\?:\s*string\[\]/);
|
|
assert.match(service, /comment\?:\s*string/);
|
|
});
|
|
|
|
test('训练页保留任务卡、对话区、回复区和结果区并互斥切换', async () => {
|
|
const source = await pageSource('../src/pages/user/practice/index.vue');
|
|
|
|
assert.match(source, /scenario-card/);
|
|
assert.match(source, /assignment-panel/);
|
|
assert.match(source, /dialogue-card/);
|
|
assert.match(source, /reply-card/);
|
|
assert.match(source, /result-card/);
|
|
assert.match(source, /class="result-actions"/);
|
|
assert.match(source, />再练一次<\/button>/);
|
|
assert.match(source, /practiceView === 'prep'/);
|
|
assert.match(source, /practiceView === 'active'/);
|
|
assert.match(source, /practiceView\.value === 'result'/);
|
|
assert.match(source, /result\.score\?\.length/);
|
|
assert.match(source, /class="result-dimensions"/);
|
|
});
|
|
|
|
test('底部固定导航页面复用安全操作区', async () => {
|
|
const [base, practice, assign, review] = await Promise.all([
|
|
pageSource('../src/styles/base.css'),
|
|
pageSource('../src/pages/user/practice/index.vue'),
|
|
pageSource('../src/pages/supervisor/assign/index.vue'),
|
|
pageSource('../src/pages/supervisor/review/index.vue')
|
|
]);
|
|
|
|
assert.match(base, /--app-bottom-nav-space/);
|
|
assert.match(base, /\.tabbar-safe-action\s*\{/);
|
|
assert.match(practice, /primary-button tabbar-safe-action/);
|
|
assert.match(assign, /primary-button submit-button tabbar-safe-action/);
|
|
assert.match(review, /primary-button submit-button tabbar-safe-action/);
|
|
});
|
|
|
|
test('员工练习页显示当前岗位而不是固定生活顾问', async () => {
|
|
const source = await pageSource('../src/pages/user/practice/index.vue');
|
|
|
|
assert.match(source, /getSelectedPosition/);
|
|
assert.match(source, /const positionLabel = ref\(getSelectedPosition\(\) \|\| '生活顾问'\)/);
|
|
assert.match(source, /\{\{ positionLabel \}\} · 业主服务场景/);
|
|
assert.doesNotMatch(source, /scenario-desc">生活顾问 · 业主服务场景/);
|
|
});
|
|
|
|
test('员工问师傅页显示当前岗位而不是固定生活顾问', async () => {
|
|
const source = await pageSource('../src/pages/user/sop/index.vue');
|
|
|
|
assert.match(source, /getSelectedPosition/);
|
|
assert.match(source, /const positionLabel = ref\(getSelectedPosition\(\)\)/);
|
|
assert.match(source, /\{\{ positionLabel \}\} · 住宅项目/);
|
|
assert.doesNotMatch(source, /sop-context">管家(生活顾问) · 住宅项目/);
|
|
});
|
|
|
|
test('训练页最近练过保留派发任务来源', async () => {
|
|
const source = await pageSource('../src/pages/user/practice/index.vue');
|
|
|
|
assert.match(source, /sourceLabel: assignmentSourceLabel\(item\.source\)/);
|
|
assert.match(source, /class="assignment-badge"/);
|
|
assert.match(source, /source === 'retry'[\s\S]*错题再练/);
|
|
assert.match(source, /source === 'camp'[\s\S]*训练营/);
|
|
});
|
|
|
|
test('学习页展示真实错题本并可进入已有再练任务', async () => {
|
|
const [source, practiceService] = await Promise.all([
|
|
pageSource('../src/pages/user/learning/index.vue'),
|
|
pageSource('../src/services/practice.ts')
|
|
]);
|
|
|
|
assert.match(source, /错题本/);
|
|
assert.match(source, /getPracticeMistakes/);
|
|
assert.match(source, /retryAssignmentId/);
|
|
assert.match(source, /pendingAssignmentKey/);
|
|
assert.match(source, /mistakeFilters/);
|
|
assert.match(source, /visibleMistakes/);
|
|
assert.match(source, /source === 'retry'[\s\S]*错题再练/);
|
|
assert.match(source, /source === 'camp'[\s\S]*训练营/);
|
|
assert.match(practiceService, /\/api\/aihr\/mobile\/practice\/mistakes/);
|
|
});
|
|
|
|
test('学习页允许员工确认本人正式岗前任务完成', async () => {
|
|
const [source, practiceService] = await Promise.all([
|
|
pageSource('../src/pages/user/learning/index.vue'),
|
|
pageSource('../src/services/practice.ts')
|
|
]);
|
|
|
|
assert.match(source, /completeOnboarding/);
|
|
assert.match(source, /确认完成/);
|
|
assert.match(practiceService, /\/api\/aihr\/mobile\/onboard\/tasks\/\$\{id\}\/complete/);
|
|
});
|
|
|
|
test('学习页显示岗位 SOP 适配状态而不冒充上岗资格', async () => {
|
|
const [source, knowledgeService] = await Promise.all([
|
|
pageSource('../src/pages/user/learning/index.vue'),
|
|
pageSource('../src/services/knowledge.ts')
|
|
]);
|
|
|
|
assert.match(source, /positionSopSummary/);
|
|
assert.match(source, /正式上岗资格/);
|
|
assert.match(knowledgeService, /\/api\/knowledge\/position-sop/);
|
|
});
|
|
|
|
test('员工画像明确参考等级与人工贡献分语义', async () => {
|
|
const source = await pageSource('../src/pages/user/profile/index.vue');
|
|
|
|
assert.match(source, /训练画像等级(参考)/);
|
|
assert.match(source, /人工贡献分/);
|
|
});
|