const fs = require("fs"); const vm = require("vm"); const elements = new Map(); function element(id) { if (!elements.has(id)) { elements.set(id, { id, textContent: "", innerHTML: "", hidden: false, disabled: false, value: "", dataset: {}, classList: { toggle() {} }, setAttribute(name, value) { this[name] = value; }, addEventListener() {}, focus() {}, }); } 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, }; 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("app/static/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 条"); console.log("smoke test passed");