Files
xsk-education-management/app/scripts/smoke_test.js
T
2026-07-06 13:35:55 +08:00

257 lines
7.7 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.
const fs = require("fs");
const path = require("path");
const vm = require("vm");
const staticDir = path.resolve(__dirname, "../app/static");
function domNode(id, extras = {}) {
let value = "";
return {
id,
textContent: "",
innerHTML: "",
hidden: false,
disabled: false,
dataset: {},
classList: { toggle() {} },
get value() {
return value;
},
set value(nextValue) {
value = String(nextValue ?? "");
},
setAttribute(name, nextValue) {
this[name] = nextValue;
},
addEventListener() {},
focus() {},
...extras,
};
}
const elements = new Map();
function element(id) {
if (!elements.has(id)) {
elements.set(id, domNode(id));
}
return elements.get(id);
}
const documentStub = {
querySelector(selector) {
return element(selector.replace(/^#/, ""));
},
querySelectorAll() {
return [];
},
addEventListener() {},
createElement(tag) {
return element(`created-${tag}-${elements.size}`);
},
body: { appendChild() {} },
execCommand() {
return true;
},
};
const context = {
console,
document: documentStub,
navigator: {},
fetch: async () => ({ ok: true, json: async () => ({ classnotes: { mtime: 0 } }) }),
Date,
JSON,
Map,
Number,
String,
};
context.window = { location: { href: "" } };
context.confirm = () => true;
context.alert = () => {};
function assertEqual(name, actual, expected) {
if (actual !== expected) {
throw new Error(`${name}: got ${actual}, expected ${expected}`);
}
}
function value(expression) {
return vm.runInContext(expression, context);
}
vm.createContext(context);
vm.runInContext(fs.readFileSync(path.join(staticDir, "app.js"), "utf8"), context);
vm.runInContext(
`
currentRecords = [
{ date: "2026.06.12", weekday: "星期五", time: "10:00-11:00", student: "甲", duration: "1小时0分", duration_hours: 1, teacher: "王老师", subject: "英语", _recordIndex: 0, _recordKey: "a" },
{ date: "2026.06.12", weekday: "星期五", time: "08:00-09:00", student: "乙", duration: "1小时0分", duration_hours: 1, teacher: "王老师", subject: "英语", _recordIndex: 1, _recordKey: "b" },
{ date: "2026.06.13", weekday: "星期六", time: "09:00-10:00", student: "丙", duration: "1小时0分", duration_hours: 1, teacher: "王老师", subject: "英语", _recordIndex: 2, _recordKey: "c" },
{ date: "2026.06.12", weekday: "星期五", time: "07:00-08:00", student: "丁", duration: "1小时0分", duration_hours: 1, teacher: "李老师", subject: "数学", _recordIndex: 3, _recordKey: "d" },
];
renderGroupedRecords(currentRecords);
`,
context,
);
assertEqual("初始日期按钮文案", value("dateSortBtn.textContent"), "日期 升序排列");
assertEqual("初始时间按钮文案", value("timeSortBtn.textContent"), "时间 升序排列");
assertEqual("默认组内排序", value("currentRecordOrder.join('')"), "bacd");
vm.runInContext("toggleRecordSort('date')", context);
assertEqual("日期降序按钮文案", value("dateSortBtn.textContent"), "日期 降序排列");
assertEqual("日期降序组内排序", value("currentRecordOrder.join('')"), "cbad");
vm.runInContext("toggleRecordSort('date'); toggleRecordSort('time')", context);
assertEqual("时间降序按钮文案", value("timeSortBtn.textContent"), "时间 降序排列");
assertEqual("日期升序时间降序组内排序", value("currentRecordOrder.join('')"), "abcd");
vm.runInContext(
`
correctedRecords.set("a", { ...currentRecords[0], date: "2026.06.11", weekday: "星期四", time: "07:00-08:00" });
renderGroupedRecords(currentRecords);
`,
context,
);
assertEqual("纠错后排序仍使用显示记录", value("currentRecordOrder.join('')"), "abcd");
vm.runInContext("updateCorrectionToolbar('测试提交按钮')", context);
assertEqual("提交审核按钮文案", value("submitCorrectionsBtn.textContent"), "提交审核 1 条");
vm.runInContext(
`
const longSummaryBody = "完整课程小结内容".repeat(20);
currentRecords = [
{
date: "2026.06.14",
weekday: "星期日",
time: "10:00-11:00",
student: "甲",
duration: "1小时0分",
duration_hours: 1,
teacher: "王老师",
subject: "英语",
_recordIndex: 0,
_recordKey: "summary-test",
summary_count: 1,
summaries: [{ title: "课程小结", body: longSummaryBody, teacher: "王老师", subject: "英语" }],
},
];
expandedSummaryRecords = new Set(["summary-test"]);
collapsedTeacherGroups = new Set();
const summaryHtml = renderGroupedRecords(currentRecords);
if (!summaryHtml.includes(longSummaryBody)) {
throw new Error("课程小结展开后应直接显示完整正文");
}
if (summaryHtml.includes("展开全文") || summaryHtml.includes("summary-toggle") || summaryHtml.includes("summary-preview")) {
throw new Error("课程小结展开后不应再渲染展开全文按钮或预览裁切");
}
`,
context,
);
const adminElements = new Map();
function adminElement(id) {
if (!adminElements.has(id)) {
adminElements.set(id, domNode(id, {
scrollIntoView() {},
}));
}
return adminElements.get(id);
}
const adminFetchUrls = [];
const adminContext = {
console,
navigator: {},
Date,
JSON,
Map,
Number,
String,
URLSearchParams,
window: { location: { href: "" } },
confirm: () => true,
alert: () => {},
document: {
querySelector(selector) {
return adminElement(selector.replace(/^#/, ""));
},
querySelectorAll() {
return [];
},
addEventListener() {},
createElement(tag) {
return adminElement(`created-${tag}-${adminElements.size}`);
},
body: { appendChild() {} },
execCommand() {
return true;
},
},
fetch: async (url) => {
adminFetchUrls.push(String(url));
if (String(url).startsWith("/api/students")) {
return {
ok: true,
json: async () => ({
summary: { total: 1, debt: 0, warning: 0, normal: 1 },
count: 1,
students: [
{
student_id: "XS001",
student: "甲",
primary_entry_year: 2020,
account_status: "正常",
remaining: 12,
remaining_duration: "12小时",
payments: [{ date: "2026-06-01", hours: 12, duration: "12小时" }],
note: "备注",
},
],
}),
};
}
return {
ok: true,
json: async () => ({
ok: true,
students_count: 1,
active_teachers_count: 0,
records_count: 0,
course_summaries_count: 0,
current_month_hours: 0,
current_month_duration: "0小时",
overview: {},
students: { summary: {}, low_remaining: [] },
teachers: {},
course_summaries: {},
tasks: {},
teaching: {},
period: {},
}),
};
},
};
vm.createContext(adminContext);
vm.runInContext(fs.readFileSync(path.join(staticDir, "admin.js"), "utf8"), adminContext);
vm.runInContext("loadStudents()", adminContext);
setTimeout(() => {
if (!adminFetchUrls.some((url) => url.startsWith("/api/students"))) {
throw new Error(`学生档案列表未调用 /api/students${adminFetchUrls.join(",")}`);
}
const rowsHtml = adminElements.get("studentRows").innerHTML;
if (!rowsHtml.includes("XS001") || !rowsHtml.includes("2020")) {
throw new Error("学生档案列表未渲染学生ID和入学年份");
}
vm.runInContext('openEditStudentEditor("XS001")', adminContext);
const payload = vm.runInContext("buildStudentPayload()", adminContext);
if (Object.prototype.hasOwnProperty.call(payload, "remaining")) {
throw new Error("学生档案保存 payload 不应包含 remaining");
}
assertEqual("学生档案剩余课时只读展示", adminElements.get("editRemaining").value, "12小时");
console.log("smoke test passed");
}, 0);