Files
CareerNexus/tests/test_frontend_logic.js
T
RoyirinRosmontis ac6d08cdf2 前端答辩级改造:错误中文化、证据弹窗可读化、图谱默认岗位子图、演化报告化、匹配60/40、六步导览
- cnFetch 统一网络层,业务化错误文案+原位重试,屏蔽内部文件名
- 证据弹窗:结论/来源平台·企业·日期/原文/命中依据+翻页
- 图谱默认 Java 岗位子图;详情面板四区块(结论/能力清单/近期变化/证据操作)
- 演化页本期结论块(需求指数口径)+近8季默认视图
- 匹配页输入区 60/40 + 岗位信息卡 + 结论分档文案 + 缺口折叠
- 学习路径空状态引导+阶段语义化命名;六步导览浮动条
- tests/test_frontend_logic.js 21 用例
2026-08-13 12:33:13 +08:00

75 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 关键前端逻辑单测(node 直跑):错误翻译 / 结论分档 / 阶段命名 / 本期结论分组 */
"use strict";
const fs = require("fs");
const path = require("path");
const src = fs.readFileSync(path.join(__dirname, "..", "webui", "app.js"), "utf-8");
function extractFn(name) {
const m = src.match(new RegExp(`function ${name}\\([\\s\\S]*?\\n}`));
if (!m) throw new Error(`未找到函数 ${name}`);
return m[0];
}
function loadFn(name) {
// 在独立函数作用域内定义并返回目标函数(规避 eval 严格模式作用域问题)
return new Function(`${extractFn(name)}; return ${name};`)();
}
const friendlyErr = loadFn("friendlyErr");
const matchAdvice = loadFn("matchAdvice");
let pass = 0, fail = 0;
function eq(actual, expected, label) {
const ok = actual === expected;
if (ok) pass++; else { fail++; console.error(`FAIL ${label}: 期望 ${JSON.stringify(expected)} 实际 ${JSON.stringify(actual)}`); }
}
function includes(hay, needle, label) {
const ok = String(hay).includes(needle);
if (ok) pass++; else { fail++; console.error(`FAIL ${label}: ${JSON.stringify(hay)} 不含 ${needle}`); }
}
/* friendlyErr:禁止出现 Failed to fetch / 内部文件名 / 接口路径 */
eq(friendlyErr(new TypeError("Failed to fetch")), "暂时无法连接服务,请检查后端是否已启动", "fetch 失败翻译");
eq(friendlyErr(new Error("NetworkError when attempting to fetch resource.")), "暂时无法连接服务,请检查后端是否已启动", "NetworkError 翻译");
eq(friendlyErr(new Error("记录不存在")), "记录不存在", "服务端中文 detail 原样保留");
eq(friendlyErr(new Error("HTTP 500")), "服务响应异常(500)", "HTTP 状态码中文化");
eq(friendlyErr(new Error("batch_2019.jsonl#92 读取失败")), "操作失败,请稍后重试", "内部文件名不外显");
eq(friendlyErr(new Error("")), "操作失败,请稍后重试", "空消息兜底");
/* matchAdvice:三档结论 */
includes(matchAdvice(85), "强烈推荐", ">=80 档");
includes(matchAdvice(72), "补齐核心缺口", "60-80 档");
includes(matchAdvice(40), "先系统学习", "<60 档");
includes(matchAdvice(80), "强烈推荐", "边界 80");
includes(matchAdvice(60), "补齐核心缺口", "边界 60");
includes(matchAdvice(59.9), "先系统学习", "边界 59.9");
/* 阶段语义化命名(与 renderPath 内逻辑一致) */
const STAGE_NAMES = ["基础补齐", "岗位核心", "项目验证"];
const stageName = (i) => STAGE_NAMES[i] || "综合提升";
eq(stageName(0), "基础补齐", "阶段1命名");
eq(stageName(1), "岗位核心", "阶段2命名");
eq(stageName(2), "项目验证", "阶段3命名");
eq(stageName(3), "综合提升", "阶段4顺延");
/* 本期结论分组逻辑(与 renderEvoSummary 一致) */
const evoEvents = [
{ window: "2026Q2", change_type: "新增", skill: "Kubernetes", prev_weight: 0, curr_weight: 2.52 },
{ window: "2026Q2", change_type: "增强", skill: "微服务", prev_weight: 1.3, curr_weight: 2.1 },
{ window: "2026Q3", change_type: "减弱", skill: "JSP", prev_weight: 0.8, curr_weight: 0.2 },
{ window: "2026Q3", change_type: "退出", skill: "Struts", prev_weight: 0.1, curr_weight: 0 },
];
const wins = [...new Set(evoEvents.map((e) => e.window))].sort().slice(-2);
eq(wins.join(","), "2026Q2,2026Q3", "最近两个季度窗口");
const verb = { "新增": "成为新要求", "增强": "需求显著增强", "减弱": "需求明显减弱", "退出": "退出能力画像" };
eq(verb[evoEvents[1].change_type], "需求显著增强", "增强动词");
const line = `${evoEvents[1].skill}(需求指数 ${evoEvents[1].prev_weight.toFixed(2)}→${evoEvents[1].curr_weight.toFixed(2)})`;
eq(line, "微服务(需求指数 1.30→2.10)", "需求指数文案");
/* 热度走势默认近 8 季切片 */
const heat = Array.from({ length: 30 }, (_, i) => [`20${10 + i}Q1`, i]);
eq(heat.slice(-8).length, 8, "近8季切片");
eq(heat.slice(-Infinity).length, 30, "全部范围");
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);