import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { dayLessons, dayNoteSkeleton, dayNoteTitle, lessonHeading, missingHeadings } from '../src/core/day-note.ts'; import { subjectFromHeading } from '../src/core/notes.ts'; import type { UntisLesson, UntisTimetable } from '../src/core/untis.ts'; function lesson(overrides: Partial & { periodId: number }): UntisLesson { return { lessonId: 1, date: '2026-09-18', start: '08:00', end: '08:45', statuses: ['REGULAR'], cancelled: false, changed: false, subjects: [{ name: 'DE', longName: 'Deutsch' }], teachers: [{ name: 'MEI', longName: 'Meier' }], rooms: [{ name: '204' }], classes: [], replaced: { subjects: [], teachers: [], rooms: [] }, notes: {}, homework: [], online: false, ...overrides, }; } function timetable(lessons: UntisLesson[]): UntisTimetable { return { from: '2026-09-18', to: '2026-09-18', days: [{ date: '2026-09-18', lessons, holidays: [] }] }; } describe('dayNoteTitle', () => { it('is the weekday and the date, as a person would write it', () => { assert.equal(dayNoteTitle('2026-09-18'), 'Freitag, 18.09.2026'); }); }); describe('lessonHeading', () => { it('leads with the subject, then the time, teacher and room', () => { assert.equal(lessonHeading(lesson({ periodId: 1 }), 0), '1. Deutsch — 08:00–08:45 · MEI · R 204'); }); it('marks a substitution, because the teacher is not the usual one', () => { assert.match(lessonHeading(lesson({ periodId: 1, changed: true }), 0), /Vertretung$/); }); it('survives a period with no subject at all', () => { assert.match(lessonHeading(lesson({ periodId: 1, subjects: [] }), 2), /^3\. Stunde — /); }); it('writes a heading subjectFromHeading reads back — the loop that makes lessons searchable', () => { // These two have to agree or a day's notes index under nothing: the page // writes the heading, the indexer reads the subject out of it again. for (const [index, entry] of [lesson({ periodId: 1 }), lesson({ periodId: 2, changed: true })].entries()) { assert.equal(subjectFromHeading(lessonHeading(entry, index)), 'Deutsch'); } }); }); describe('dayLessons', () => { it('leaves out a cancelled period, which taught nothing', () => { const lessons = dayLessons( timetable([lesson({ periodId: 1 }), lesson({ periodId: 2, cancelled: true, start: '08:50', end: '09:35' })]), '2026-09-18', ); assert.deepEqual(lessons.map((entry) => entry.periodId), [1]); }); it('keeps a substitution, which did happen', () => { const lessons = dayLessons(timetable([lesson({ periodId: 1, changed: true })]), '2026-09-18'); assert.equal(lessons[0]?.changed, true); }); it('is empty for a day the timetable does not cover', () => { assert.deepEqual(dayLessons(timetable([lesson({ periodId: 1 })]), '2026-09-19'), []); }); }); describe('dayNoteSkeleton', () => { it('is a heading per lesson with room to write under each', () => { const text = dayNoteSkeleton(dayLessons(timetable([lesson({ periodId: 1 }), lesson({ periodId: 2, start: '08:50', end: '09:35' })]), '2026-09-18')); assert.equal(text.match(/^## /gm)?.length, 2); assert.match(text, /^## 1\. Deutsch/m); }); it('is empty when there are no lessons, rather than a lone heading', () => { assert.equal(dayNoteSkeleton([]), ''); }); }); describe('missingHeadings', () => { const lessons = dayLessons( timetable([lesson({ periodId: 1 }), lesson({ periodId: 2, start: '08:50', end: '09:35' })]), '2026-09-18', ); it('is empty when the note already has them', () => { assert.deepEqual(missingHeadings(dayNoteSkeleton(lessons), lessons), []); }); it('names the lesson a note started early does not have yet', () => { const started = '## 1. Deutsch — 08:00–08:45 · MEI · R 204\n\nErörterung.\n'; assert.deepEqual(missingHeadings(started, lessons).map((entry) => entry.periodId), [2]); }); it('recognises a heading the person shortened', () => { // "2. Deutsch" is still the second period; adding it again would give the // day two of them. assert.deepEqual(missingHeadings('## 1. Kurz\n## 2. Auch kurz\n', lessons), []); }); it('is everything for an empty note', () => { assert.equal(missingHeadings('', lessons).length, 2); }); });