import assert from 'node:assert/strict'; import test, { after, before } from 'node:test'; import puppeteer from 'puppeteer'; import { isoFromText, parseAttachmentLinks, parseListHtml, parseTaskHtml } from '../src/api/iserv-parse.js'; // Fictional pages in the layouts of IServ (older and current), the same the IServ filter project tests its Python // parsers with. These parsers run inside a browser page, so the tests run them in one. const LIST_OLDER = `
TitelBeginnAbgabeTags
Bruchrechnung üben01.10.202610.10.2026 23:59Mathe
Referat02.10.202615.10.2026 12:00(keine)
Vokabeln Unit 428.09.202605.10.2026 08:00Englisch✓
`; // Current layout: status icon first, no start column, teacher feedback (Rückmeldungen) right after the due date, // an empty tag as an italic placeholder. const ROW = ({ status, id, title, due, tags }) => ` ${title} Rückmeldung von Frau Zimmermann
${tags}
`; const LIST_CURRENT = ` ${ROW({ status: 'Erledigt', id: 201, title: 'Gedicht lernen', due: '20261010235900', tags: 'keine' })} ${ROW({ status: 'Neu', id: 202, title: 'Versuchsprotokoll', due: '20261012080000', tags: 'Physik' })}
AufgabeAbgabeterminRückmeldungenTags
`; const DATES = `
Erstellt vonStartterminAbgabetermin
Frau Zimmermann01.10.2026 08:00
  • 10.10.2026 23:59
`; const TASK_PAGE = (left, right) => `
Erika Musterfrau
${left}
${right}
Eigene Abgabe Wolf
Rückmeldung von Kaiser: gut gemacht
`; const DESCRIPTION = `

Lerne das Gedicht auswendig bis Freitag.

Frau Hofmann fragt ab.
Viel Erfolg!

`; const FILES = `
Blatt.pdf
`; const PAGE_WITH_FILES = TASK_PAGE(DESCRIPTION + '
' + DATES, FILES); const PAGE_WITHOUT_FILES = TASK_PAGE(DESCRIPTION, '
' + DATES); // no files: the date table sits in the other column const PAGE_WITHOUT_DESCRIPTION = TASK_PAGE('', '
' + DATES); const PAGE_OLDER = `
Erstellt von:Frau Zimmermann
Teilnehmer:Klasse 7b Ahrens
Beschreibung:
Vokabeln lernen.
Rückmeldungen
Rückmeldung von Kaiser: gut gemacht
Eigene Abgabe Wolf
`; let browser; let page; before(async () => { browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); page = await browser.newPage(); }); after(async () => { await browser?.close(); }); const list = (html) => page.evaluate(parseListHtml, html); const task = (html) => page.evaluate(parseTaskHtml, html); test('older list: tags, start and due by header and data-sort, done by the status title', async () => { assert.deepEqual(await list(LIST_OLDER), { 101: { tags: 'Mathe', start: '2026-10-01', due: '2026-10-10T23:59', done: false, expired: false }, 102: { tags: '', start: '2026-10-02', due: '2026-10-15T12:00', done: false, expired: false }, 103: { tags: 'Englisch', start: '2026-09-28', due: '2026-10-05T08:00', done: true, expired: false }, }); }); test('current list: the column after the due date (teacher feedback) is never taken for tags', async () => { assert.deepEqual(await list(LIST_CURRENT), { 201: { tags: '', start: null, due: '2026-10-10T23:59', done: true, expired: false }, 202: { tags: 'Physik', start: null, due: '2026-10-12T08:00', done: false, expired: false }, }); }); test('task page: only the description, with its paragraphs and line breaks, and the start date', async () => { const expected = { description: 'Lerne das Gedicht auswendig bis Freitag.\nFrau Hofmann fragt ab.\nViel Erfolg!', start: '2026-10-01T08:00' }; for (const html of [PAGE_WITH_FILES, PAGE_WITHOUT_FILES]) { assert.deepEqual(await task(html), expected); } }); test('task page without a description gives "" and never submission or feedback', async () => { assert.deepEqual(await task(PAGE_WITHOUT_DESCRIPTION), { description: '', start: '2026-10-01T08:00' }); }); test('older task page: the block after the "Beschreibung:" label, no start date', async () => { assert.deepEqual(await task(PAGE_OLDER), { description: 'Vokabeln lernen.', start: null }); }); test('status icon "Abgelaufen" marks the task expired, not done', async () => { const html = ` ${ROW({ status: 'Abgelaufen', id: 301, title: 'Alt', due: '20260920120000', tags: 'Mathe' })}
AufgabeAbgabetermin
`; const result = await list(html); assert.equal(result[301].expired, true); assert.equal(result[301].done, false); }); test('attachment links: the download link of each row, the name from the name link, menu toggles skipped', async () => { const html = `
Blatt.pdf
Bild.png⋮
Eigene.docx
`; assert.deepEqual(await page.evaluate(parseAttachmentLinks, html), [ { href: '/iserv/fs/file/exercise/1', filename: 'Blatt.pdf' }, { href: '/iserv/fs/download/9', filename: 'Bild.png' }, ]); }); test('a page without the provided-files form has no attachment links', async () => { assert.deepEqual(await page.evaluate(parseAttachmentLinks, PAGE_WITHOUT_FILES), []); }); test('isoFromText reads dd.mm.yyyy with an optional time', () => { assert.equal(isoFromText('Do, 24.09.2026 14:30 Uhr'), '2026-09-24T14:30'); assert.equal(isoFromText('5.10.2026'), '2026-10-05'); assert.equal(isoFromText('Morgen'), null); assert.equal(isoFromText(undefined), null); });