initial commit

This commit is contained in:
2026-10-16 22:08:45 +03:00
commit 19ee41411b
97 changed files with 15141 additions and 0 deletions

82
test/lessons.test.js Normal file
View File

@@ -0,0 +1,82 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { parseDocx } from "../server/parser.js";
import {
lessonKey,
diffSchedules,
changeCount,
personalFilter,
subgroupsOf,
} from "../shared/lessons.js";
const fef = () =>
parseDocx(readFileSync(new URL("./fixtures/fef-23-ba.docx", import.meta.url)), {
name: "23-ЛГ_23-БА.docx",
});
const find = (s, group, test) =>
s.lessons.find((l) => l.groups.includes(group) && test(l));
test("Ключ занятия не зависит от строки таблицы и переживает перепубликацию", () => {
const a = fef(),
b = fef();
// Same content parsed twice: same keys, no changes.
assert.deepEqual(
a.lessons.map((l) => lessonKey(l, "23-БА")),
b.lessons.map((l) => lessonKey(l, "23-БА")),
);
assert.equal(changeCount(diffSchedules(a, b, "23-БА")), 0);
const moved = { ...a.lessons[0], id: "other-row" };
assert.equal(lessonKey(moved, "23-БА"), lessonKey(a.lessons[0], "23-БА"));
});
test("Сравнение версий: аудитория, перенос, новое и удалённое занятие", () => {
const before = fef(),
after = fef();
const room = find(after, "23-БА", (l) => l.day === 5 && l.start === "08:30");
room.room = "333А";
const pr = find(after, "23-БА", (l) => l.day === 5 && l.subject === "PR-менеджмент");
pr.start = "11:45";
const risk = find(after, "23-БА", (l) => l.day === 3 && l.subject === "Риск-менеджмент");
after.lessons.splice(after.lessons.indexOf(risk), 1);
after.lessons.push({ ...pr, id: "new", day: 6, start: "08:30", subject: "Новый предмет" });
// A change for the other group must not reach 23-БА.
find(after, "23-ЛГ", (l) => l.room).room = "999";
const diff = diffSchedules(before, after, "23-БА");
assert.deepEqual(diff.added.map((l) => l.subject), ["Новый предмет"]);
assert.deepEqual(diff.removed.map((l) => l.subject), ["Риск-менеджмент"]);
assert.deepEqual(
diff.changed.map((c) => [c.after.subject, c.fields.map((f) => `${f.field}:${f.from}→${f.to}`)]),
[
["Инновационный менеджмент", ["room:333→333А"]],
["PR-менеджмент", ["start:10:05→11:45"]],
],
);
assert.equal(changeCount(diff), 4);
});
test("Изменённая пометка недель видна как изменение, а не как новое занятие", () => {
const before = fef(),
after = fef();
find(after, "23-БА", (l) => l.condition === "6-13н").condition = "6-14н";
const diff = diffSchedules(before, after, "23-БА");
assert.equal(diff.added.length + diff.removed.length, 0);
assert.deepEqual(diff.changed[0].fields.map((f) => [f.label, f.from, f.to]), [
["Недели", "6-13н", "6-14н"],
]);
});
test("Подгруппа и скрытые занятия фильтруются только для своей группы", () => {
const s = fef();
assert.deepEqual(subgroupsOf(s, "23-ЛГ"), ["Подгруппа 1", "Подгруппа 2"]);
assert.deepEqual(subgroupsOf(s, "23-БА"), []);
const first = find(s, "23-ЛГ", (l) => l.subgroups.some((g) => g.label === "Подгруппа 1"));
const second = find(s, "23-ЛГ", (l) => l.subgroups.some((g) => g.label === "Подгруппа 2"));
const whole = find(s, "23-ЛГ", (l) => !l.subgroups.length);
const prefs = { subgroup: "Подгруппа 2", hidden: [lessonKey(whole, "23-ЛГ")] };
assert.equal(personalFilter(first, "23-ЛГ", prefs), "other-subgroup");
assert.equal(personalFilter(second, "23-ЛГ", prefs), "shown");
assert.equal(personalFilter(whole, "23-ЛГ", prefs), "hidden");
assert.equal(personalFilter(first, "23-ЛГ", null), "shown");
});