feat(aihr): complete phase two pilot readiness
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
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 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();
|
||||
});
|
||||
|
||||
const candidateView = await vite.ssrLoadModule('/src/services/candidate-view.ts');
|
||||
const { buildCandidateFlow, candidateRoutes, formatCandidateFileSize, materialStatusTone } = candidateView;
|
||||
|
||||
const record = (overrides = {}) => ({
|
||||
time: '2026-07-10 10:00:00',
|
||||
candidate: '候选人0001',
|
||||
position: '生活顾问',
|
||||
score: 86,
|
||||
result: '建议进入下一轮',
|
||||
summary: '沟通完整',
|
||||
...overrides
|
||||
});
|
||||
|
||||
const material = (overrides = {}) => ({
|
||||
id: 1,
|
||||
ossId: 2,
|
||||
fileName: '简历.pdf',
|
||||
materialType: '补充资料',
|
||||
fileSize: 2048,
|
||||
status: '待审核',
|
||||
time: '2026-07-10 10:30:00',
|
||||
...overrides
|
||||
});
|
||||
|
||||
const pageSource = async (path) => readFile(new URL(path, import.meta.url), 'utf8');
|
||||
|
||||
test('候选流程从面试、补资料、审核到完成只依赖真实记录', () => {
|
||||
assert.deepEqual(buildCandidateFlow([], []), {
|
||||
stage: 'interview',
|
||||
badge: '待面试',
|
||||
title: '完成结构化面试',
|
||||
desc: '按岗位题目完成回答,提交后生成正式面试记录。',
|
||||
progress: 0,
|
||||
progressText: '0/2',
|
||||
notice: '待完成结构化面试',
|
||||
cta: '开始面试',
|
||||
route: candidateRoutes.interview
|
||||
});
|
||||
|
||||
assert.equal(buildCandidateFlow([record()], []).stage, 'materials');
|
||||
assert.equal(buildCandidateFlow([record()], []).route, candidateRoutes.materials);
|
||||
assert.equal(buildCandidateFlow([record()], [material({ status: '已驳回' })]).badge, '资料被驳回');
|
||||
assert.equal(buildCandidateFlow([record()], [material()]).stage, 'review');
|
||||
assert.equal(buildCandidateFlow([record()], [material()]).route, candidateRoutes.progress);
|
||||
assert.equal(buildCandidateFlow([record()], [material({ status: '已通过' })]).stage, 'complete');
|
||||
assert.equal(buildCandidateFlow([record()], [material({ status: '已通过' })]).progress, 100);
|
||||
});
|
||||
|
||||
test('资料展示格式保持三态且文件大小可读', () => {
|
||||
assert.equal(materialStatusTone('已通过'), 'passed');
|
||||
assert.equal(materialStatusTone('已驳回'), 'rejected');
|
||||
assert.equal(materialStatusTone('待审核'), 'pending');
|
||||
assert.equal(formatCandidateFileSize(0), '0KB');
|
||||
assert.equal(formatCandidateFileSize(2048), '2KB');
|
||||
assert.equal(formatCandidateFileSize(1572864), '1.5MB');
|
||||
});
|
||||
|
||||
test('候选首页只做状态总览和独立页面分流', async () => {
|
||||
const source = await pageSource('../src/pages/candidate/index/index.vue');
|
||||
|
||||
assert.match(source, /getCandidateInterviewRecords/);
|
||||
assert.match(source, /getCandidateMaterials/);
|
||||
Object.values(candidateRoutes).forEach((route) => assert.match(source, new RegExp(route.replaceAll('/', '\\/'))));
|
||||
assert.doesNotMatch(source, /startCandidateInterview/);
|
||||
assert.doesNotMatch(source, /uploadCandidateMaterial/);
|
||||
assert.doesNotMatch(source, /searchKnowledge/);
|
||||
assert.doesNotMatch(source, /interviewQuestions/);
|
||||
});
|
||||
|
||||
test('面试、资料、进度和预习页各自只承载一类真实任务', async () => {
|
||||
const [interview, materials, progress, study] = await Promise.all([
|
||||
pageSource('../src/pages/candidate/interview/index.vue'),
|
||||
pageSource('../src/pages/candidate/materials/index.vue'),
|
||||
pageSource('../src/pages/candidate/progress/index.vue'),
|
||||
pageSource('../src/pages/candidate/study/index.vue')
|
||||
]);
|
||||
|
||||
assert.match(interview, /startCandidateInterview/);
|
||||
assert.match(interview, /saveInterviewAnswer/);
|
||||
assert.match(interview, /finishCandidateInterview/);
|
||||
assert.doesNotMatch(interview, /uploadCandidateMaterial|searchKnowledge/);
|
||||
|
||||
assert.match(materials, /chooseCandidateFile/);
|
||||
assert.match(materials, /uploadCandidateMaterial/);
|
||||
assert.doesNotMatch(materials, /startCandidateInterview|searchKnowledge/);
|
||||
|
||||
assert.match(progress, /buildCandidateFlow/);
|
||||
assert.match(progress, /getCandidateInterviewRecords/);
|
||||
assert.match(progress, /getCandidateMaterials/);
|
||||
assert.doesNotMatch(progress, /startCandidateInterview|uploadCandidateMaterial|searchKnowledge/);
|
||||
|
||||
assert.match(study, /searchKnowledge/);
|
||||
assert.doesNotMatch(study, /startCandidateInterview|uploadCandidateMaterial/);
|
||||
});
|
||||
|
||||
test('五个候选页面登录都使用 redirectTo 并保留当前任务页', async () => {
|
||||
const sources = await Promise.all([
|
||||
pageSource('../src/pages/candidate/index/index.vue'),
|
||||
pageSource('../src/pages/candidate/interview/index.vue'),
|
||||
pageSource('../src/pages/candidate/materials/index.vue'),
|
||||
pageSource('../src/pages/candidate/progress/index.vue'),
|
||||
pageSource('../src/pages/candidate/study/index.vue')
|
||||
]);
|
||||
|
||||
sources.forEach((source) => {
|
||||
const goLogin = source.match(/const goLogin = \(\) => \{\n[\s\S]*?\n\};/)?.[0] || '';
|
||||
assert.match(goLogin, /rememberLoginRedirect\(currentPath\)/);
|
||||
assert.match(goLogin, /uni\.redirectTo\(\{ url: '\/pages\/auth\/login\/index' \}\)/);
|
||||
assert.doesNotMatch(goLogin, /uni\.navigateTo\(/);
|
||||
});
|
||||
});
|
||||
|
||||
test('候选子路由已注册为真实页面', async () => {
|
||||
const pages = await pageSource('../src/pages.json');
|
||||
|
||||
assert.match(pages, /pages\/candidate\/interview\/index/);
|
||||
assert.match(pages, /pages\/candidate\/materials\/index/);
|
||||
assert.match(pages, /pages\/candidate\/progress\/index/);
|
||||
assert.match(pages, /pages\/candidate\/study\/index/);
|
||||
});
|
||||
@@ -0,0 +1,367 @@
|
||||
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 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();
|
||||
});
|
||||
|
||||
const supervisorView = await vite.ssrLoadModule('/src/services/supervisor-view.ts');
|
||||
const {
|
||||
actionablePracticeAlerts,
|
||||
buildSupervisorMetrics,
|
||||
buildTeamProfiles,
|
||||
createAsyncGenerationGuard,
|
||||
inferSupervisorScenarioId,
|
||||
isReviewEditable,
|
||||
isSupervisorAccessDenied,
|
||||
reviewCompletionAction,
|
||||
supervisorScenarioOptions
|
||||
} = supervisorView;
|
||||
|
||||
const assignPageSource = await readFile(
|
||||
new URL('../src/pages/supervisor/assign/index.vue', import.meta.url),
|
||||
'utf8'
|
||||
);
|
||||
const reviewPageSource = await readFile(
|
||||
new URL('../src/pages/supervisor/review/index.vue', import.meta.url),
|
||||
'utf8'
|
||||
);
|
||||
const dashboardPageSource = await readFile(
|
||||
new URL('../src/pages/supervisor/index/index.vue', import.meta.url),
|
||||
'utf8'
|
||||
);
|
||||
const teamPageSource = await readFile(
|
||||
new URL('../src/pages/supervisor/team/index.vue', import.meta.url),
|
||||
'utf8'
|
||||
);
|
||||
const reviewsPageSource = await readFile(
|
||||
new URL('../src/pages/supervisor/reviews/index.vue', import.meta.url),
|
||||
'utf8'
|
||||
);
|
||||
const practiceServiceSource = await readFile(
|
||||
new URL('../src/services/practice.ts', import.meta.url),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const review = (overrides = {}) => ({
|
||||
id: 1,
|
||||
extPartyId: '13800138000',
|
||||
time: '2026-07-10 09:00:00',
|
||||
trainee: '张三',
|
||||
scene: '投诉接待',
|
||||
score: 86,
|
||||
status: '待复盘',
|
||||
summary: '训练摘要',
|
||||
...overrides
|
||||
});
|
||||
|
||||
const assignment = (overrides = {}) => ({
|
||||
id: 1,
|
||||
extPartyId: '13800138000',
|
||||
traineeName: '张三',
|
||||
scenarioId: 'complaint-water',
|
||||
scenarioName: '投诉接待',
|
||||
source: 'manual',
|
||||
reason: '主管指派',
|
||||
status: '待训练',
|
||||
createTime: '2026-07-10 09:00:00',
|
||||
...overrides
|
||||
});
|
||||
|
||||
const alert = (overrides = {}) => ({
|
||||
type: 'low_score',
|
||||
title: '低分训练预警',
|
||||
desc: '0 条训练低于 80 分',
|
||||
severity: 'high',
|
||||
...overrides
|
||||
});
|
||||
|
||||
test('daily 任务不计入主管待训练指标,也不创建团队成员', () => {
|
||||
const assignments = [
|
||||
assignment({ id: 1, extPartyId: 'daily-user', traineeName: '每日任务员工', source: 'daily' }),
|
||||
assignment({ id: 2, extPartyId: 'manual-user', traineeName: '专项员工' }),
|
||||
assignment({ id: 3, extPartyId: 'done-user', traineeName: '已完成员工', status: '已完成' })
|
||||
];
|
||||
|
||||
const metrics = buildSupervisorMetrics([], assignments, []);
|
||||
const profiles = buildTeamProfiles([], assignments);
|
||||
|
||||
assert.equal(metrics.find((item) => item.key === 'pendingAssignments')?.value, 1);
|
||||
assert.deepEqual(profiles.map((item) => item.extPartyId).sort(), ['done-user', 'manual-user']);
|
||||
});
|
||||
|
||||
test('低分指标只取 alerts 的后端聚合值', () => {
|
||||
const metrics = buildSupervisorMetrics(
|
||||
[review({ score: 32 })],
|
||||
[],
|
||||
[alert({ desc: '7 条训练低于 80 分,建议派发定向再练' })]
|
||||
);
|
||||
|
||||
assert.equal(metrics.find((item) => item.key === 'lowScores')?.value, 7);
|
||||
assert.equal(buildSupervisorMetrics([review({ score: 32 })], [], [])
|
||||
.find((item) => item.key === 'lowScores')?.value, 0);
|
||||
});
|
||||
|
||||
test('按 extPartyId 聚合复盘和专项,并区分同名成员', () => {
|
||||
const profiles = buildTeamProfiles(
|
||||
[
|
||||
review({ id: 1, extPartyId: '13800138000', trainee: '张三', score: 76 }),
|
||||
review({ id: 2, extPartyId: '13800138000', trainee: '张三', score: 91, status: '已复盘' })
|
||||
],
|
||||
[
|
||||
assignment({ id: 1, extPartyId: '13800138000', traineeName: '张三' }),
|
||||
assignment({ id: 2, extPartyId: '13900139000', traineeName: '张三', status: '已完成' })
|
||||
]
|
||||
);
|
||||
|
||||
assert.equal(profiles.length, 2);
|
||||
assert.deepEqual(
|
||||
profiles.map((item) => item.displayName).sort(),
|
||||
['张三 · 尾号8000', '张三 · 尾号9000'].sort()
|
||||
);
|
||||
const first = profiles.find((item) => item.extPartyId === '13800138000');
|
||||
assert.equal(first?.pendingReview, 1);
|
||||
assert.equal(first?.pendingAssignment, 1);
|
||||
assert.equal(first?.latestScore, 76);
|
||||
assert.equal(first?.latestScene, '投诉接待');
|
||||
});
|
||||
|
||||
test('低于 80 分为高风险,有待复盘为中风险,其余为低风险', () => {
|
||||
const profiles = buildTeamProfiles(
|
||||
[
|
||||
review({ id: 1, extPartyId: 'high', trainee: '高风险', score: 79 }),
|
||||
review({ id: 2, extPartyId: 'medium', trainee: '中风险', score: 88 }),
|
||||
review({ id: 3, extPartyId: 'low', trainee: '低风险', score: 95, status: '已复盘' })
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
assert.equal(profiles.find((item) => item.extPartyId === 'high')?.risk, 'high');
|
||||
assert.equal(profiles.find((item) => item.extPartyId === 'medium')?.risk, 'medium');
|
||||
assert.equal(profiles.find((item) => item.extPartyId === 'low')?.risk, 'low');
|
||||
});
|
||||
|
||||
test('主管页面共享三个业务场景及文本推荐规则', () => {
|
||||
assert.deepEqual(supervisorScenarioOptions.map((item) => item.id), [
|
||||
'fee-parking',
|
||||
'complaint-water',
|
||||
'repair-elevator-delay'
|
||||
]);
|
||||
assert.equal(inferSupervisorScenarioId('电梯故障沟通低分'), 'repair-elevator-delay');
|
||||
assert.equal(inferSupervisorScenarioId('投诉接待需要补练'), 'complaint-water');
|
||||
assert.equal(inferSupervisorScenarioId('催缴物业费'), 'fee-parking');
|
||||
});
|
||||
|
||||
test('stable 提示不计入团队预警指标', () => {
|
||||
const alerts = [
|
||||
alert({ type: 'stable', title: '团队状态稳定', desc: '当前无新增风险', severity: 'low' }),
|
||||
alert({ type: 'pending_review', title: '待复盘积压', desc: '2 条对练等待主管复盘', severity: 'medium' })
|
||||
];
|
||||
const metrics = buildSupervisorMetrics([], [], alerts);
|
||||
const alertMetric = metrics.find((item) => item.key === 'alerts');
|
||||
|
||||
assert.equal(alertMetric?.value, 1);
|
||||
assert.equal(alertMetric?.desc, '请及时跟进风险项');
|
||||
assert.deepEqual(actionablePracticeAlerts(alerts).map((item) => item.type), ['pending_review']);
|
||||
assert.deepEqual(alerts.map((item) => item.type), ['stable', 'pending_review']);
|
||||
assert.equal(buildSupervisorMetrics([], [], [
|
||||
alert({ type: 'stable', title: '团队状态稳定', desc: '当前无新增风险', severity: 'low' })
|
||||
]).find((item) => item.key === 'alerts')?.value, 0);
|
||||
});
|
||||
|
||||
test('画像按时间显示最新训练,但任一未复盘低分仍保持高风险', () => {
|
||||
const profiles = buildTeamProfiles([
|
||||
review({
|
||||
id: 1,
|
||||
time: '2026-07-08 09:00:00',
|
||||
scene: '旧低分场景',
|
||||
score: 62,
|
||||
status: '待复盘'
|
||||
}),
|
||||
review({
|
||||
id: 2,
|
||||
time: '2026-07-10 10:30:00',
|
||||
scene: '新高分场景',
|
||||
score: 93,
|
||||
status: '已复盘'
|
||||
})
|
||||
], []);
|
||||
const profile = profiles[0];
|
||||
|
||||
assert.equal(profile.latestScore, 93);
|
||||
assert.equal(profile.latestScoreText, '93分');
|
||||
assert.equal(profile.latestScene, '新高分场景');
|
||||
assert.equal(profile.lastPracticeAt, '2026-07-10 10:30:00');
|
||||
assert.equal(profile.risk, 'high');
|
||||
assert.match(profile.reason, /未复盘低分/);
|
||||
});
|
||||
|
||||
test('组织快照中的稳定成员即使没有训练记录也保留在画像中', () => {
|
||||
const profiles = buildTeamProfiles([], [], [
|
||||
{ extPartyId: 'stable-1', name: '稳定员工' }
|
||||
]);
|
||||
|
||||
assert.equal(profiles.length, 1);
|
||||
assert.equal(profiles[0].extPartyId, 'stable-1');
|
||||
assert.equal(profiles[0].latestScoreText, '暂无评分');
|
||||
assert.equal(profiles[0].risk, 'low');
|
||||
});
|
||||
|
||||
test('组织快照姓名不可读时用稳定身份标识展示', () => {
|
||||
const profiles = buildTeamProfiles([], [], [
|
||||
{ extPartyId: '13900001111', name: '???????' },
|
||||
{ extPartyId: 'employee-project-A-000123', name: '????????A_000123' }
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
profiles.map((item) => item.displayName).sort(),
|
||||
['员工 尾号1111', '员工 末尾000123'].sort()
|
||||
);
|
||||
assert.equal(profiles.every((item) => !item.name.includes('?')), true);
|
||||
assert.equal(profiles.every((item) => !item.reason.includes('?')), true);
|
||||
});
|
||||
|
||||
test('只有待复盘可编辑,成功返回仅在上一页为 reviews 时后退', () => {
|
||||
assert.equal(isReviewEditable('待复盘'), true);
|
||||
assert.equal(isReviewEditable('已复盘'), false);
|
||||
assert.equal(reviewCompletionAction('pages/supervisor/reviews/index'), 'back');
|
||||
assert.equal(reviewCompletionAction('/pages/supervisor/reviews/index'), 'back');
|
||||
assert.equal(reviewCompletionAction('pages/supervisor/index/index'), 'replace');
|
||||
assert.equal(reviewCompletionAction(undefined), 'replace');
|
||||
});
|
||||
|
||||
test('异步代次守卫拒绝刷新后的旧结果和组件销毁后的结果', () => {
|
||||
const guard = createAsyncGenerationGuard();
|
||||
const audioGeneration = guard.capture();
|
||||
|
||||
assert.equal(guard.isCurrent(audioGeneration), true);
|
||||
guard.invalidate();
|
||||
assert.equal(guard.isCurrent(audioGeneration), false);
|
||||
|
||||
const historyGeneration = guard.begin();
|
||||
assert.equal(guard.isCurrent(historyGeneration), true);
|
||||
guard.dispose();
|
||||
assert.equal(guard.isCurrent(historyGeneration), false);
|
||||
});
|
||||
|
||||
test('只把主管权限错误识别为访问拒绝', () => {
|
||||
assert.equal(isSupervisorAccessDenied(new Error('无主管权限,无法访问主管功能')), true);
|
||||
assert.equal(isSupervisorAccessDenied('当前账号无主管权限'), true);
|
||||
assert.equal(isSupervisorAccessDenied(new Error('登录已过期,请重新登录')), false);
|
||||
assert.equal(isSupervisorAccessDenied(new Error('网络请求失败')), false);
|
||||
assert.equal(isSupervisorAccessDenied({ message: '无主管权限' }), false);
|
||||
assert.equal(isSupervisorAccessDenied(null), false);
|
||||
});
|
||||
|
||||
test('复盘详情只允许待复盘提交并保留未保存草稿', () => {
|
||||
assert.match(reviewPageSource, /const editable = computed\(\(\) => isReviewEditable\(detail\.value\?\.status\)\)/);
|
||||
assert.match(reviewPageSource, /v-if="editable" class="panel review-form"/);
|
||||
assert.match(reviewPageSource, /v-else class="panel review-complete"/);
|
||||
assert.match(reviewPageSource, /if \(!editable\.value\)[\s\S]*该记录已完成复盘/);
|
||||
assert.match(reviewPageSource, /onShow\(\(\) => \{[\s\S]*refreshAuthState\(\);[\s\S]*if \(!loggedIn\.value\) \{[\s\S]*accessDenied\.value = false;[\s\S]*clearDetail\(\);\s*return;\s*\}[\s\S]*if \(!detail\.value && !loading\.value\)[\s\S]*loadDetail\(\)/);
|
||||
});
|
||||
|
||||
test('复盘详情登录和完成导航不堆叠页面', () => {
|
||||
assert.match(reviewPageSource, /rememberLoginRedirect\(currentPath\(\)\);\s*uni\.redirectTo/);
|
||||
assert.match(reviewPageSource, /reviewCompletionAction\(previousRoute\)/);
|
||||
assert.match(reviewPageSource, /uni\.navigateBack\(\{ delta: 1, fail: goToReviews \}\)/);
|
||||
assert.match(reviewPageSource, /const goToReviews = \(\) => \{\s*uni\.redirectTo/);
|
||||
});
|
||||
|
||||
test('复盘音频晚到结果被释放且不会写入页面状态', () => {
|
||||
assert.match(reviewPageSource, /const audioRequests = createAsyncGenerationGuard\(\)/);
|
||||
assert.match(reviewPageSource, /const generation = audioRequests\.capture\(\)/);
|
||||
assert.match(reviewPageSource, /if \(!audioRequests\.isCurrent\(generation\)\) \{\s*releaseAuthenticatedFileUrl\(source\);\s*return;/);
|
||||
assert.match(reviewPageSource, /audioRequests\.dispose\(\)/);
|
||||
});
|
||||
|
||||
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 =/);
|
||||
});
|
||||
|
||||
test('指派中锁定返回和刷新,写入成功后强制启动新历史读取', () => {
|
||||
assert.match(assignPageSource, /class="subpage-back-button"[\s\S]*:disabled="assigning"/);
|
||||
assert.match(assignPageSource, /:disabled="historyLoading \|\| assigning"\s*@click="loadAssignments\(\)"/);
|
||||
assert.match(assignPageSource, /const loadAssignments = async \(force = false\)/);
|
||||
assert.match(assignPageSource, /const generation = historyRequests\.begin\(\)/);
|
||||
assert.match(assignPageSource, /await loadAssignments\(true\)/);
|
||||
assert.match(assignPageSource, /const goBack = \(\) => \{\s*if \(assigning\.value\) return;/);
|
||||
});
|
||||
|
||||
test('指派页销毁时废止历史请求,晚到结果不能写回', () => {
|
||||
assert.match(assignPageSource, /import \{ computed, onBeforeUnmount, ref \} from 'vue'/);
|
||||
assert.match(assignPageSource, /onBeforeUnmount\(\(\) => \{\s*historyRequests\.dispose\(\);\s*\}\)/);
|
||||
});
|
||||
|
||||
test('五个主管页面优先显示统一无权限状态并清理私密数据', () => {
|
||||
const pages = [
|
||||
{ source: dashboardPageSource, clear: 'clearData' },
|
||||
{ source: teamPageSource, clear: 'clearData' },
|
||||
{ source: assignPageSource, clear: 'clearPrivateState' },
|
||||
{ source: reviewsPageSource, clear: 'clearData' },
|
||||
{ source: reviewPageSource, clear: 'clearDetail' }
|
||||
];
|
||||
|
||||
pages.forEach(({ source, clear }) => {
|
||||
assert.match(source, /const accessDenied = ref\(false\)/);
|
||||
assert.match(source, /<view v-if="accessDenied" class="panel supervisor-access-panel">[\s\S]*无主管权限[\s\S]*当前账号不是主管或项目经理,无法查看团队训练数据。[\s\S]*@click="goToEmployee"[\s\S]*返回员工端[\s\S]*@click="switchAccount"[\s\S]*切换账号[\s\S]*<\/view>\s*<view v-else-if="!loggedIn"/);
|
||||
assert.match(source, new RegExp(`if \\(isSupervisorAccessDenied\\(error\\)\\) \\{[\\s\\S]*${clear}\\(\\);[\\s\\S]*accessDenied\\.value = true;[\\s\\S]*return;`));
|
||||
assert.match(source, /if \(!loggedIn\.value\) \{[\s\S]*accessDenied\.value = false;/);
|
||||
assert.match(source, /accessDenied\.value = false;[\s\S]*(?:snapshot|nextReviews|detail\.value|nextAssignments)/);
|
||||
assert.match(source, /const goToEmployee = \(\) => \{\s*uni\.switchTab\(\{ url: '\/pages\/user\/today\/index' \}\);\s*\};/);
|
||||
assert.match(source, /const switchAccount = \(\) => \{\s*clearAuth\(\);\s*rememberLoginRedirect\([\s\S]*?\);\s*uni\.redirectTo\(\{ url: '\/pages\/auth\/login\/index' \}\);\s*\};/);
|
||||
});
|
||||
});
|
||||
|
||||
test('指派页通过团队快照在进入时校验主管权限', () => {
|
||||
assert.match(assignPageSource, /getPracticeTeamSnapshot/);
|
||||
assert.match(assignPageSource, /const snapshot = await getPracticeTeamSnapshot\(\)/);
|
||||
assert.match(assignPageSource, /assignments\.value = snapshot\.assignments/);
|
||||
assert.doesNotMatch(assignPageSource, /getPracticeAssignments/);
|
||||
});
|
||||
|
||||
test('指派页保留 H5 查询参数二次解码', () => {
|
||||
assert.match(assignPageSource, /decodeURIComponent\(value\)\.trim\(\)/);
|
||||
});
|
||||
|
||||
test('主管 dashboard 和 team 使用团队快照,reviews 继续使用待复盘接口', () => {
|
||||
assert.match(practiceServiceSource, /export const getPracticeTeamSnapshot = \(\) =>/);
|
||||
assert.match(practiceServiceSource, /url: '\/api\/aihr\/mobile\/practice\/team'/);
|
||||
assert.match(dashboardPageSource, /getPracticeTeamSnapshot/);
|
||||
assert.match(dashboardPageSource, /buildTeamProfiles\(records\.value, assignments\.value, members\.value\)/);
|
||||
assert.match(teamPageSource, /getPracticeTeamSnapshot/);
|
||||
assert.match(teamPageSource, /buildTeamProfiles\(records\.value, assignments\.value, members\.value\)/);
|
||||
assert.match(reviewsPageSource, /getPracticeReviews/);
|
||||
assert.doesNotMatch(reviewsPageSource, /getPracticeTeamSnapshot/);
|
||||
});
|
||||
|
||||
test('五个主管页面登录都使用 redirectTo 避免返回栈重复', () => {
|
||||
const pages = [
|
||||
dashboardPageSource,
|
||||
teamPageSource,
|
||||
reviewsPageSource,
|
||||
assignPageSource,
|
||||
reviewPageSource
|
||||
];
|
||||
|
||||
pages.forEach((source) => {
|
||||
const goLogin = source.match(/const goLogin = \(\) => \{\n[\s\S]*?\n\};/)?.[0] || '';
|
||||
assert.match(goLogin, /rememberLoginRedirect\(/);
|
||||
assert.match(goLogin, /uni\.redirectTo\(\{ url: '\/pages\/auth\/login\/index' \}\)/);
|
||||
assert.doesNotMatch(goLogin, /uni\.navigateTo\(/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user