feat: restore learning feedback and knowledge capabilities
This commit is contained in:
@@ -151,3 +151,20 @@ test('候选子路由已注册为真实页面', async () => {
|
||||
assert.match(pages, /pages\/candidate\/progress\/index/);
|
||||
assert.match(pages, /pages\/candidate\/study\/index/);
|
||||
});
|
||||
|
||||
test('候选面试生成题目和评分结果恢复语音播报', async () => {
|
||||
const interview = await pageSource('../src/pages/candidate/interview/index.vue');
|
||||
|
||||
assert.match(interview, /createSpeechPlaybackController/);
|
||||
assert.match(interview, /question-voice-button/);
|
||||
assert.match(interview, /speakQuestion/);
|
||||
assert.match(interview, /interviewResultSpeech/);
|
||||
assert.match(interview, /停止播报/);
|
||||
assert.match(interview, /onUnload/);
|
||||
assert.match(interview, /onHide\(\(\) => \{[\s\S]*speechPlayback\.stop\(\);[\s\S]*cancelQuestionRecording\(\);/);
|
||||
assert.match(interview, /const generation = \+\+questionCaptureGeneration/);
|
||||
assert.match(interview, /generation !== questionCaptureGeneration/);
|
||||
assert.match(interview, /recordingQuestionStarting\.value/);
|
||||
assert.match(interview, /abortQuestionTranscription\?\.\(\)/);
|
||||
assert.match(interview, /generation !== questionTranscriptionGeneration/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8');
|
||||
|
||||
test('全网问AI与企业问师傅分入口分接口并持续显示外部来源提示', async () => {
|
||||
const [sop, webAi, webService] = await Promise.all([
|
||||
read('../src/pages/user/sop/index.vue'),
|
||||
read('../src/pages/user/web-ai/index.vue'),
|
||||
read('../src/services/web-ai.ts')
|
||||
]);
|
||||
assert.match(sop, /企业问师傅/);
|
||||
assert.match(sop, /问 AI(全网)/);
|
||||
assert.match(webAi, /不与企业 SOP 混用/);
|
||||
assert.match(webAi, /答案来自互联网,不代表公司制度/);
|
||||
assert.match(webService, /\/api\/aihr\/web-ai\/query/);
|
||||
assert.doesNotMatch(webService, /\/api\/knowledge\/query/);
|
||||
});
|
||||
|
||||
test('问题榜、最佳答案、积分分账和无现金边界都有真实页面', async () => {
|
||||
const [routes, list, detail, points, service] = await Promise.all([
|
||||
read('../src/pages.json'),
|
||||
read('../src/pages/user/questions/index.vue'),
|
||||
read('../src/pages/user/questions/detail.vue'),
|
||||
read('../src/pages/user/points/index.vue'),
|
||||
read('../src/services/questions.ts')
|
||||
]);
|
||||
for (const route of ['pages/user/questions/index', 'pages/user/questions/detail', 'pages/user/questions/publish', 'pages/user/points/index']) {
|
||||
assert.match(routes, new RegExp(route.replaceAll('/', '\\/')));
|
||||
}
|
||||
assert.match(list, /HOT/);
|
||||
assert.match(list, /UNANSWERED/);
|
||||
assert.match(list, /REWARDED/);
|
||||
assert.match(list, /MINE/);
|
||||
assert.match(detail, /最佳答案/);
|
||||
assert.match(points, /积分和学习学分,不是现金、不能提现/);
|
||||
assert.match(service, /\/api\/aihr\/community\/questions/);
|
||||
assert.match(service, /\/api\/aihr\/community\/points\/me/);
|
||||
});
|
||||
|
||||
test('员工考试与主管组卷路由、题目播报、真实人员多选和幂等发布齐全', async () => {
|
||||
const [routes, employee, edit, service] = await Promise.all([
|
||||
read('../src/pages.json'),
|
||||
read('../src/pages/user/exam/index.vue'),
|
||||
read('../src/pages/supervisor/exams/edit.vue'),
|
||||
read('../src/services/exam.ts')
|
||||
]);
|
||||
for (const route of ['pages/user/exam/index', 'pages/supervisor/exams/index', 'pages/supervisor/exams/edit', 'pages/supervisor/exams/results']) {
|
||||
assert.match(routes, new RegExp(route.replaceAll('/', '\\/')));
|
||||
}
|
||||
assert.match(employee, /播报题目/);
|
||||
assert.match(employee, /submitMyExam/);
|
||||
assert.match(edit, /getPracticeTeamSnapshot/);
|
||||
assert.match(edit, /最多100人/);
|
||||
assert.match(edit, /requestId/);
|
||||
assert.match(service, /\/api\/aihr\/mobile\/exams\/supervisor/);
|
||||
});
|
||||
|
||||
test('每日题和专项可反馈,主管可按岗位筛选并对常见难题一键派专项', async () => {
|
||||
const [practice, assign, team, service] = await Promise.all([
|
||||
read('../src/pages/user/practice/index.vue'),
|
||||
read('../src/pages/supervisor/assign/index.vue'),
|
||||
read('../src/pages/supervisor/team/index.vue'),
|
||||
read('../src/services/practice.ts')
|
||||
]);
|
||||
assert.match(practice, /这道题对工作有帮助吗/);
|
||||
assert.match(practice, /这个专项对工作有帮助吗/);
|
||||
assert.match(assign, /场景、SOP 与题目按岗位版本加载/);
|
||||
for (const position of ['生活顾问', '客服', '保安', '保洁', '保修']) assert.match(assign, new RegExp(position));
|
||||
assert.match(team, /一键派专项/);
|
||||
assert.match(team, /近30天 · 仅当前项目/);
|
||||
assert.match(service, /\/practice\/assignments\/\$\{assignmentId\}\/feedback/);
|
||||
assert.match(service, /\/practice\/difficulties\/assign/);
|
||||
});
|
||||
|
||||
test('老年用户可读性与窄屏布局保持最低字号、表单宽度和四栏标签完整', async () => {
|
||||
const [base, sop, questions] = await Promise.all([
|
||||
read('../src/styles/base.css'),
|
||||
read('../src/pages/user/sop/index.vue'),
|
||||
read('../src/pages/user/questions/index.vue')
|
||||
]);
|
||||
assert.match(base, /\.input\s*\{[\s\S]*?box-sizing:\s*border-box/);
|
||||
assert.match(base, /\.textarea\s*\{[\s\S]*?box-sizing:\s*border-box/);
|
||||
assert.doesNotMatch(sop, /font-size:\s*(?:[0-9]|1[01])px/);
|
||||
assert.match(questions, /grid-template-columns:\s*repeat\(4,\s*minmax\(0,\s*1fr\)\)/);
|
||||
});
|
||||
@@ -4,13 +4,15 @@ import test from 'node:test';
|
||||
|
||||
const pageSource = async (path) => readFile(new URL(path, import.meta.url), 'utf8');
|
||||
|
||||
test('全局基础样式不对输入控件设置 border-box', async () => {
|
||||
test('共享输入框使用 border-box 且不覆盖原生控件', async () => {
|
||||
const source = await pageSource('../src/styles/base.css');
|
||||
const textareaStyle = source.match(/\.textarea\s*\{[\s\S]*?\n\}/)?.[0] || '';
|
||||
|
||||
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);
|
||||
assert.doesNotMatch(source, /(^|\n)input\s*\{[^}]*box-sizing:\s*border-box/m);
|
||||
assert.doesNotMatch(source, /(^|\n)textarea\s*\{[^}]*box-sizing:\s*border-box/m);
|
||||
assert.match(textareaStyle, /box-sizing:\s*border-box/);
|
||||
});
|
||||
|
||||
test('SOP 查询框不再覆盖 textarea 的 box-sizing', async () => {
|
||||
@@ -35,6 +37,7 @@ test('训练页提交失败会回滚本次乐观追加的对话', async () => {
|
||||
|
||||
assert.match(source, /const previousTurns = turns\.value\.slice\(\)/);
|
||||
assert.match(source, /turns\.value = previousTurns/);
|
||||
assert.match(source, /uni\.showToast\(\{ title: message\.value \|\| '提交失败', icon: 'none'/);
|
||||
});
|
||||
|
||||
test('manifest 明确声明为 uni-app Vue3 CLI 工程', async () => {
|
||||
@@ -100,6 +103,25 @@ test('普通员工请求主管深链时回到员工首页', async () => {
|
||||
assert.match(source, /return '\/pages\/user\/today\/index'/);
|
||||
});
|
||||
|
||||
test('主管可切换到学员显示模式且组织角色保持不变', async () => {
|
||||
const [positionSource, authSource, supervisorProfile, userProfile] = await Promise.all([
|
||||
pageSource('../src/services/position.ts'),
|
||||
pageSource('../src/services/auth.ts'),
|
||||
pageSource('../src/pages/supervisor/profile/index.vue'),
|
||||
pageSource('../src/pages/user/profile/index.vue')
|
||||
]);
|
||||
|
||||
assert.match(authSource, /supervisorLearnerModeKey/);
|
||||
assert.match(authSource, /enterSupervisorLearnerMode/);
|
||||
assert.match(authSource, /leaveSupervisorLearnerMode/);
|
||||
assert.match(authSource, /经理.*负责人.*组长/);
|
||||
assert.match(authSource, /项目管理/);
|
||||
assert.match(positionSource, /isSupervisorLearnerMode/);
|
||||
assert.match(supervisorProfile, /进入学员模式/);
|
||||
assert.match(userProfile, /返回主管工作台/);
|
||||
assert.doesNotMatch(supervisorProfile, /setStorageSync\(roleKey,\s*'user'/);
|
||||
});
|
||||
|
||||
test('员工首页保留师傅区、语音条、今日安排、我的地图四段结构', async () => {
|
||||
const source = await pageSource('../src/pages/user/today/index.vue');
|
||||
|
||||
@@ -133,6 +155,7 @@ test('员工首页今日安排保留派发任务来源', async () => {
|
||||
assert.match(source, /source === 'retry'[\s\S]*错题再练/);
|
||||
assert.match(source, /source === 'camp'[\s\S]*训练营/);
|
||||
assert.match(source, /class="schedule-source"/);
|
||||
assert.match(source, /今天先完成每日三题/);
|
||||
});
|
||||
|
||||
test('问师傅页保留搜索框、答案区和总结卡入口', async () => {
|
||||
@@ -141,6 +164,8 @@ test('问师傅页保留搜索框、答案区和总结卡入口', async () => {
|
||||
assert.match(source, /query-panel/);
|
||||
assert.match(source, /answer-panel/);
|
||||
assert.match(source, /summary-panel/);
|
||||
assert.match(source, /MY_PRACTICE_SUMMARY/);
|
||||
assert.match(source, /TEAM_PRACTICE_SUMMARY/);
|
||||
});
|
||||
|
||||
test('训练页保留任务卡、对话区、回复区和结果区并互斥切换', async () => {
|
||||
@@ -230,3 +255,178 @@ test('员工画像明确参考等级与人工贡献分语义', async () => {
|
||||
assert.match(source, /训练画像等级(参考)/);
|
||||
assert.match(source, /人工贡献分/);
|
||||
});
|
||||
|
||||
test('我的页面提供全局三档字号并持久化同步', async () => {
|
||||
const [profileSource, accessibilitySource, appSource, baseSource] = await Promise.all([
|
||||
pageSource('../src/pages/user/profile/index.vue'),
|
||||
pageSource('../src/services/accessibility.ts'),
|
||||
pageSource('../src/App.vue'),
|
||||
pageSource('../src/styles/base.css')
|
||||
]);
|
||||
|
||||
assert.match(accessibilitySource, /label:\s*'标准'/);
|
||||
assert.match(accessibilitySource, /label:\s*'大字'/);
|
||||
assert.match(accessibilitySource, /label:\s*'超大字'/);
|
||||
assert.match(profileSource, /setAppFontSize/);
|
||||
assert.match(accessibilitySource, /aihr_mobile_font_size/);
|
||||
assert.match(accessibilitySource, /data-aihr-font-size/);
|
||||
assert.match(appSource, /applyStoredFontSize/);
|
||||
assert.match(baseSource, /data-aihr-font-size='large'\]\s+body[\s\S]*?zoom:\s*1\.12/);
|
||||
assert.match(baseSource, /data-aihr-font-size='extra-large'\]\s+body[\s\S]*?zoom:\s*1\.25/);
|
||||
});
|
||||
|
||||
test('员工岗位只能来自人力系统且个人页不再允许手动切换', async () => {
|
||||
const [profileSource, positionSource, authSource] = await Promise.all([
|
||||
pageSource('../src/pages/user/profile/index.vue'),
|
||||
pageSource('../src/pages/auth/position/index.vue'),
|
||||
pageSource('../src/services/auth.ts')
|
||||
]);
|
||||
|
||||
assert.match(profileSource, /岗位来自人力系统组织同步/);
|
||||
assert.doesNotMatch(profileSource, /changePosition/);
|
||||
assert.match(positionSource, /员工端不支持手动切换岗位/);
|
||||
assert.doesNotMatch(positionSource, /saveSelectedPosition|selectPosition/);
|
||||
assert.match(authSource, /positionSourceKey/);
|
||||
assert.match(authSource, /saveOrgPosition/);
|
||||
});
|
||||
|
||||
test('主管专项从正式团队名单按姓名多选并一次批量派发', async () => {
|
||||
const [source, practiceService] = await Promise.all([
|
||||
pageSource('../src/pages/supervisor/assign/index.vue'),
|
||||
pageSource('../src/services/practice.ts')
|
||||
]);
|
||||
|
||||
assert.match(source, /selectedMemberIds/);
|
||||
assert.match(source, /v-for="member in teamMembers"/);
|
||||
assert.match(source, /member\.name \|\| '未命名员工'/);
|
||||
assert.match(source, /snapshot\.members/);
|
||||
assert.match(source, /targets: selectedMembers\.map/);
|
||||
assert.match(source, /createPracticeAssignmentsBatch/);
|
||||
assert.match(source, /当天/);
|
||||
assert.match(source, /明天/);
|
||||
assert.match(source, /3天内/);
|
||||
assert.match(source, /7天内/);
|
||||
assert.match(source, /dueDate: dueDate\.value/);
|
||||
assert.match(source, /batchResultItems/);
|
||||
assert.match(source, /const maxAssignmentTargets = 100/);
|
||||
assert.match(source, /teamMembers\.value\.slice\(0, maxAssignmentTargets\)/);
|
||||
assert.match(source, /persistPendingBatchDraft/);
|
||||
assert.match(source, /restorePendingBatchDraft/);
|
||||
assert.match(source, /persistedDraft\?\.fingerprint === fingerprint/);
|
||||
assert.match(source, /:aria-pressed="isMemberSelected\(member\.extPartyId\)"/);
|
||||
assert.match(practiceService, /\/api\/aihr\/mobile\/practice\/assignments\/batch/);
|
||||
assert.doesNotMatch(source, /placeholder="输入手机号或外部ID"/);
|
||||
});
|
||||
|
||||
test('问师傅支持现场媒体、答案播报和总结卡图片下载', async () => {
|
||||
const [source, knowledgeService, speechService, imageService] = await Promise.all([
|
||||
pageSource('../src/pages/user/sop/index.vue'),
|
||||
pageSource('../src/services/knowledge.ts'),
|
||||
pageSource('../src/services/speech.ts'),
|
||||
pageSource('../src/services/summary-card-image.ts')
|
||||
]);
|
||||
|
||||
assert.match(source, /上传图片或视频/);
|
||||
assert.match(source, /searchKnowledgeWithMedia/);
|
||||
assert.match(source, /toggleAnswerSpeech/);
|
||||
assert.match(source, /toggleSummarySpeech/);
|
||||
assert.match(source, /downloadSummaryCardImage/);
|
||||
assert.match(knowledgeService, /\/api\/knowledge\/query-media/);
|
||||
assert.match(speechService, /\/api\/ai\/tts/);
|
||||
assert.match(speechService, /SpeechSynthesisUtterance/);
|
||||
assert.match(speechService, /playWithBrowserSpeech/);
|
||||
assert.match(imageService, /toBlob/);
|
||||
assert.match(imageService, /link\.download/);
|
||||
});
|
||||
|
||||
test('问师傅恢复旧版语音提问并在录音不可用时回退选择音频', async () => {
|
||||
const [source, speechService] = await Promise.all([
|
||||
pageSource('../src/pages/user/sop/index.vue'),
|
||||
pageSource('../src/services/speech.ts')
|
||||
]);
|
||||
|
||||
assert.match(source, /语音提问/);
|
||||
assert.match(source, /toggleVoiceRecording/);
|
||||
assert.match(source, /pickVoiceAudio/);
|
||||
assert.match(source, /transcribeSpeechBlob/);
|
||||
assert.match(source, /transcribeSpeechFile/);
|
||||
assert.match(source, /navigator\.mediaDevices\?\.getUserMedia/);
|
||||
assert.match(source, /cancelVoiceRecording/);
|
||||
assert.match(speechService, /\/api\/ai\/asr/);
|
||||
});
|
||||
|
||||
test('生成的每日题和对练对话恢复语音播报入口', async () => {
|
||||
const source = await pageSource('../src/pages/user/practice/index.vue');
|
||||
|
||||
assert.match(source, /daily-question-voice/);
|
||||
assert.match(source, /turn\.role !== 'trainee'/);
|
||||
assert.match(source, /createSpeechPlaybackController/);
|
||||
assert.match(source, /停止播报/);
|
||||
});
|
||||
|
||||
test('每日题点评参考答案和训练结果均可语音播报', async () => {
|
||||
const source = await pageSource('../src/pages/user/practice/index.vue');
|
||||
|
||||
assert.match(source, /daily-feedback-card/);
|
||||
assert.match(source, /item\.feedback/);
|
||||
assert.match(source, /item\.referenceAnswer/);
|
||||
assert.match(source, /dailyFeedbackSpeech/);
|
||||
assert.match(source, /practiceResultSpeech/);
|
||||
assert.match(source, /result-voice-button/);
|
||||
});
|
||||
|
||||
test('语音、媒体和私密画像在切页或退出后不会继续写回', async () => {
|
||||
const [sopSource, practiceSource, profileSource, knowledgeService] = await Promise.all([
|
||||
pageSource('../src/pages/user/sop/index.vue'),
|
||||
pageSource('../src/pages/user/practice/index.vue'),
|
||||
pageSource('../src/pages/user/profile/index.vue'),
|
||||
pageSource('../src/services/knowledge.ts')
|
||||
]);
|
||||
|
||||
assert.match(sopSource, /onHide\(\(\) => \{[\s\S]*speechPlayback\.stop\(\);[\s\S]*cancelVoiceRecording\(\);[\s\S]*cancelPendingMediaSearch\(\);/);
|
||||
assert.match(sopSource, /activeMediaUploadTask\?\.abort\(\)/);
|
||||
assert.match(sopSource, /generation !== knowledgeSearchGeneration/);
|
||||
assert.match(sopSource, /voiceStarting\.value/);
|
||||
assert.match(sopSource, /abortVoiceTranscription\?\.\(\)/);
|
||||
assert.match(sopSource, /generation !== voiceTranscriptionGeneration/);
|
||||
assert.match(practiceSource, /onHide\(\(\) => \{[\s\S]*speechPlayback\.stop\(\);[\s\S]*cancelRecording\(\);/);
|
||||
assert.match(practiceSource, /recordingCaptureGeneration \+= 1/);
|
||||
assert.match(practiceSource, /abortTranscription\?\.\(\)/);
|
||||
assert.match(practiceSource, /generation !== transcriptionGeneration/);
|
||||
assert.match(profileSource, /let profileLoadGeneration = 0/);
|
||||
assert.match(profileSource, /getAuth\(\)\.phone !== accountPhone/);
|
||||
assert.match(profileSource, /v-if="loggedIn && profile"/);
|
||||
assert.match(profileSource, /v-if="loggedIn && evidence"/);
|
||||
assert.match(knowledgeService, /onUploadTask\?:/);
|
||||
assert.match(knowledgeService, /onUploadTask\?\.\(task\)/);
|
||||
});
|
||||
|
||||
test('播报复用已合成音频且总结图按内容高度生成并及时释放', async () => {
|
||||
const [speechService, imageService] = await Promise.all([
|
||||
pageSource('../src/services/speech.ts'),
|
||||
pageSource('../src/services/summary-card-image.ts')
|
||||
]);
|
||||
|
||||
assert.match(speechService, /const sourceCache = new Map<string, string>\(\)/);
|
||||
assert.match(speechService, /const maxCachedSources = 4/);
|
||||
assert.match(speechService, /const cachedSource = sourceCache\.get\(value\)/);
|
||||
assert.match(speechService, /sourceCache\.clear\(\)/);
|
||||
assert.match(speechService, /registerAbort\?\.\(\(\) => task\.abort\(\)\)/);
|
||||
assert.match(speechService, /registerAbort\?\.\(\(\) => xhr\.abort\(\)\)/);
|
||||
assert.match(speechService, /const isCurrent = \(\) => currentGeneration === generation && audio === context && activeKey === key/);
|
||||
assert.match(imageService, /canvas\.height = 1/);
|
||||
assert.doesNotMatch(imageService, /12000/);
|
||||
assert.match(imageService, /canvas\.width = 1;[\s\S]*canvas\.height = 1/);
|
||||
assert.match(imageService, /setTimeout\(\(\) => URL\.revokeObjectURL\(url\), 1000\)/);
|
||||
});
|
||||
|
||||
test('主管学员模式只允许查询本人训练概况且登录后保留待执行工具', async () => {
|
||||
const source = await pageSource('../src/pages/user/sop/index.vue');
|
||||
|
||||
assert.match(source, /const supervisorLearner = ref\(isSupervisorLearnerMode\(\)\)/);
|
||||
assert.match(source, /supervisorLearner\.value && toolCode === 'TEAM_PRACTICE_SUMMARY'/);
|
||||
assert.match(source, /supervisorLearner\.value && toolCode === 'TEAM_PRACTICE_SUMMARY'[\s\S]*\? 'MY_PRACTICE_SUMMARY'/);
|
||||
assert.match(source, /uni\.setStorageSync\(pendingToolKey, toolCode\)/);
|
||||
assert.match(source, /const generation = \+\+knowledgeSearchGeneration/);
|
||||
assert.match(source, /if \(!loggedIn\.value\) \{[\s\S]*goLogin\(\);[\s\S]*return;[\s\S]*\}[\s\S]*uni\.removeStorageSync\(pendingToolKey\)/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test, { after } from 'node:test';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createServer } from 'vite';
|
||||
|
||||
const storage = new Map();
|
||||
globalThis.uni = {
|
||||
getStorageSync: (key) => storage.get(key) || '',
|
||||
setStorageSync: (key, value) => storage.set(key, value),
|
||||
removeStorageSync: (key) => storage.delete(key)
|
||||
};
|
||||
|
||||
const vite = await createServer({
|
||||
root: fileURLToPath(new URL('..', import.meta.url)),
|
||||
configFile: false,
|
||||
optimizeDeps: { noDiscovery: true },
|
||||
server: { middlewareMode: true, hmr: false },
|
||||
appType: 'custom'
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await vite.close();
|
||||
delete globalThis.uni;
|
||||
});
|
||||
|
||||
const { createPageRequestScope } = await vite.ssrLoadModule('/src/services/request-scope.ts');
|
||||
|
||||
const loginAs = (phone, token) => {
|
||||
storage.set('aihr_mobile_phone', phone);
|
||||
storage.set('aihr_mobile_client_id', 'mobile-client');
|
||||
storage.set('aihr_mobile_access_token', token);
|
||||
};
|
||||
|
||||
test('同一加载通道只允许最新请求回写,不同通道可以并行', () => {
|
||||
loginAs('13800138000', 'token-a');
|
||||
const scope = createPageRequestScope();
|
||||
scope.activate();
|
||||
|
||||
const first = scope.begin('assignments');
|
||||
const parallel = scope.begin('knowledge');
|
||||
assert.equal(scope.isCurrent(first), true);
|
||||
assert.equal(scope.isCurrent(parallel), true);
|
||||
|
||||
const second = scope.begin('assignments');
|
||||
assert.equal(scope.isCurrent(first), false);
|
||||
assert.equal(scope.isCurrent(second), true);
|
||||
assert.equal(scope.isCurrent(parallel), true);
|
||||
});
|
||||
|
||||
test('切号、换 token 和页面隐藏后旧请求全部失效', () => {
|
||||
loginAs('13800138000', 'token-a');
|
||||
const scope = createPageRequestScope();
|
||||
scope.activate();
|
||||
const oldAccount = scope.begin('page');
|
||||
|
||||
loginAs('13900139000', 'token-b');
|
||||
assert.equal(scope.isCurrent(oldAccount), false);
|
||||
const newAccount = scope.begin('page');
|
||||
assert.equal(scope.isCurrent(newAccount), true);
|
||||
|
||||
storage.set('aihr_mobile_access_token', 'token-b-refreshed');
|
||||
assert.equal(scope.isCurrent(newAccount), false);
|
||||
const refreshedToken = scope.begin('page');
|
||||
assert.equal(scope.isCurrent(refreshedToken), true);
|
||||
|
||||
scope.invalidate();
|
||||
assert.equal(scope.isCurrent(refreshedToken), false);
|
||||
const hiddenRequest = scope.begin('page');
|
||||
assert.equal(scope.isCurrent(hiddenRequest), false, '页面隐藏后启动的后续请求不能重新激活页面作用域');
|
||||
});
|
||||
|
||||
test('五个账号敏感页在显示时捕获账号并在隐藏和卸载时失效', async () => {
|
||||
const pages = [
|
||||
'../src/pages/user/today/index.vue',
|
||||
'../src/pages/user/learning/index.vue',
|
||||
'../src/pages/user/practice/index.vue',
|
||||
'../src/pages/supervisor/team/index.vue',
|
||||
'../src/pages/supervisor/index/index.vue'
|
||||
];
|
||||
const sources = await Promise.all(pages.map((page) => readFile(new URL(page, import.meta.url), 'utf8')));
|
||||
|
||||
for (const source of sources) {
|
||||
assert.match(source, /currentAccountKey\(\)/);
|
||||
assert.match(source, /requests\.activate\(\)/);
|
||||
assert.match(source, /onHide\(/);
|
||||
assert.match(source, /onUnload\(/);
|
||||
assert.match(source, /requests\.invalidate\(\)/);
|
||||
assert.match(source, /requests\.isCurrent\(/);
|
||||
}
|
||||
});
|
||||
@@ -168,6 +168,28 @@ test('主管页面共享三个业务场景及文本推荐规则', () => {
|
||||
assert.equal(inferSupervisorScenarioId('催缴物业费'), 'fee-parking');
|
||||
});
|
||||
|
||||
test('专项派发使用后端批量接口并保留逐人结果和截止日期', () => {
|
||||
assert.match(assignPageSource, /selectedMemberIds/);
|
||||
assert.match(assignPageSource, /targets: selectedMembers\.map/);
|
||||
assert.match(assignPageSource, /requestId: pendingRequestId\.value/);
|
||||
assert.match(assignPageSource, /dueDate: dueDate\.value/);
|
||||
assert.match(assignPageSource, /batchResultItems\.value = result\.results/);
|
||||
assert.match(assignPageSource, /item\.status !== 'FAILED'/);
|
||||
assert.match(assignPageSource, /const maxAssignmentTargets = 100/);
|
||||
assert.match(assignPageSource, /persistPendingBatchDraft/);
|
||||
assert.match(assignPageSource, /restorePendingBatchDraft/);
|
||||
assert.match(assignPageSource, /const isPendingBatchDraft = \(value: unknown\)/);
|
||||
assert.match(assignPageSource, /draft\.selectedMemberIds\.every/);
|
||||
assert.match(assignPageSource, /pendingMatchesDeepLink/);
|
||||
assert.match(assignPageSource, /pendingDraft && \(!hasExplicitDraft \|\| pendingMatchesDeepLink\)/);
|
||||
assert.match(assignPageSource, /const assignmentDeepLink =/);
|
||||
assert.match(assignPageSource, /rememberLoginRedirect\(loginRedirectPath\.value\)/);
|
||||
assert.match(assignPageSource, /loginRedirectPath\.value = assignmentDeepLink\(requestedExtPartyId, requestedReason, requestedScenario\)/);
|
||||
assert.match(practiceServiceSource, /createPracticeAssignmentsBatch/);
|
||||
assert.match(practiceServiceSource, /\/practice\/assignments\/batch/);
|
||||
assert.doesNotMatch(assignPageSource, /for \([\s\S]*createPracticeAssignment/);
|
||||
});
|
||||
|
||||
test('stable 提示不计入团队预警指标', () => {
|
||||
const alerts = [
|
||||
alert({ type: 'stable', title: '团队状态稳定', desc: '当前无新增风险', severity: 'low' }),
|
||||
@@ -291,11 +313,11 @@ test('复盘音频晚到结果被释放且不会写入页面状态', () => {
|
||||
assert.match(reviewPageSource, /audioRequests\.dispose\(\)/);
|
||||
});
|
||||
|
||||
test('指派页登录失效清空私密状态且登录跳转不携带草稿', () => {
|
||||
test('指派页登录失效清空私密状态但保留明确深链任务', () => {
|
||||
assert.match(assignPageSource, /const clearPrivateState = \(\) => \{[\s\S]*resetDraft\(\);[\s\S]*assignments\.value = \[\]/);
|
||||
assert.match(assignPageSource, /if \(!loggedIn\.value\) return '选择员工并配置训练任务'/);
|
||||
assert.match(assignPageSource, /rememberLoginRedirect\(basePath\);\s*uni\.redirectTo/);
|
||||
assert.doesNotMatch(assignPageSource, /const loginRedirect =/);
|
||||
assert.match(assignPageSource, /rememberLoginRedirect\(loginRedirectPath\.value\);\s*uni\.redirectTo/);
|
||||
assert.match(assignPageSource, /const loginRedirectPath = ref\(basePath\)/);
|
||||
});
|
||||
|
||||
test('指派中锁定返回和刷新,写入成功后强制启动新历史读取', () => {
|
||||
|
||||
Reference in New Issue
Block a user