File size: 8,114 Bytes
e561127
 
 
 
b12cb35
e561127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b12cb35
 
 
e561127
 
 
 
 
b12cb35
 
e561127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b12cb35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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 = `<html><body><table id="crud-table"><thead><tr><th></th><th>Titel</th><th>Beginn</th><th>Abgabe</th><th>Tags</th></tr></thead><tbody>
<tr><td></td><td><a href="/iserv/exercise/show/101">Bruchrechnung üben</a></td><td data-sort="20261001">01.10.2026</td><td data-sort="20261010235900">10.10.2026 23:59</td><td>Mathe</td></tr>
<tr><td></td><td><a href="https://beispiel.iserv.eu/iserv/exercise/show/102">Referat</a></td><td data-sort="20261002">02.10.2026</td><td data-sort="20261015120000">15.10.2026 12:00</td><td>(keine)</td></tr>
<tr class="success"><td></td><td><a href="/iserv/exercise/show/103">Vokabeln Unit 4</a></td><td data-sort="20260928">28.09.2026</td><td data-sort="20261005080000">05.10.2026 08:00</td><td>Englisch</td><td><span title="Abgegeben">✓</span></td></tr>
</tbody></table></body></html>`;

// 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 }) => `<tr><td class="iserv-admin-list-field" data-sort="x">
<span class="text-center text-large" title="${status}"><span class="fal fa-circle-check"></span></span></td>
<td><a href="/iserv/exercise/show/${id}">${title}</a></td>
<td data-sort="${due}"><ul class="list-unstyled"><li>x</li><li><span class="timeAgo" data-date="2026-10-10T23:59:00+02:00">in 7 Tagen</span></li></ul></td>
<td data-sort="1"><i>Rückmeldung von Frau Zimmermann</i></td><td><div class="tags-width">${tags}</div></td><td></td></tr>`;
const LIST_CURRENT = `<html><body><table><thead><tr><th class="list-header"></th><th>Aufgabe</th><th>Abgabetermin</th><th>Rückmeldungen</th><th>Tags</th><th></th></tr></thead><tbody>
${ROW({ status: 'Erledigt', id: 201, title: 'Gedicht lernen', due: '20261010235900', tags: '<i>keine</i>' })}
${ROW({ status: 'Neu', id: 202, title: 'Versuchsprotokoll', due: '20261012080000', tags: 'Physik' })}
</tbody></table></body></html>`;

const DATES = `<table class="table"><thead><tr><th>Erstellt von</th><th>Starttermin</th><th>Abgabetermin</th></tr></thead><tbody><tr>
<td><a class="mailto" href="mailto:zimmermann@beispiel.iserv.eu">Frau Zimmermann</a></td><td>01.10.2026 08:00</td>
<td><ul><li>10.10.2026 23:59</li></ul></td></tr></tbody></table>`;
const TASK_PAGE = (left, right) => `<html><body><div role="navigation"><span class="profile-username">Erika Musterfrau</span></div>
<div id="content"><div class="panel panel-default"><div class="row"><div class="col-md-6"><div class="panel-body">${left}</div></div>
<div class="col-md-6"><div class="panel-body">${right}</div></div></div></div>
<div class="panel panel-default"><div class="panel-body"><form name="submission"><div class="text-break-word p-3">Eigene Abgabe Wolf</div></form></div></div>
<div class="panel panel-default"><div class="panel-body"><div class="text-break-word p-3">Rückmeldung von Kaiser: gut gemacht</div></div></div></div></body></html>`;
const DESCRIPTION = `<div><div class="text-break-word pb-0"><p>Lerne das Gedicht <strong>auswendig</strong> bis Freitag.</p><p>Frau Hofmann fragt ab.<br>Viel Erfolg!</p></div></div>`;
const FILES = `<form name="iserv_exercise_attachment"><table><tbody><tr><td><a class="text-break-word" href="/iserv/fs/file/exercise/1">Blatt.pdf</a></td></tr></tbody></table></form>`;
const PAGE_WITH_FILES = TASK_PAGE(DESCRIPTION + '<hr>' + DATES, FILES);
const PAGE_WITHOUT_FILES = TASK_PAGE(DESCRIPTION, '<hr>' + DATES); // no files: the date table sits in the other column
const PAGE_WITHOUT_DESCRIPTION = TASK_PAGE('', '<hr>' + DATES);
const PAGE_OLDER = `<html><body><table><tbody><tr><th>Erstellt von:</th><td>Frau Zimmermann</td></tr><tr><th>Teilnehmer:</th><td>Klasse 7b Ahrens</td></tr></tbody></table>
<div>Beschreibung:</div><div class="text-break-word">Vokabeln lernen.</div>
<div>Rückmeldungen</div><div class="text-break-word">Rückmeldung von Kaiser: gut gemacht</div>
<form name="submission"><div class="text-break-word">Eigene Abgabe Wolf</div></form></body></html>`;

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 = `<table><thead><tr><th></th><th>Aufgabe</th><th>Abgabetermin</th></tr></thead><tbody>
${ROW({ status: 'Abgelaufen', id: 301, title: 'Alt', due: '20260920120000', tags: 'Mathe' })}</tbody></table>`;
  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 = `<form name="iserv_exercise_attachment"><table><tbody>
<tr><td><a class="text-break-word" href="/iserv/fs/file/exercise/1">Blatt.pdf</a></td></tr>
<tr><td><a class="text-break-word" href="/iserv/fs/view/9">Bild.png</a></td><td><a data-toggle="dropdown" href="/">⋮</a>
<ul><li><a href="/iserv/fs/download/9">Herunterladen</a></li></ul></td></tr></tbody></table></form>
<form name="submission"><a href="/iserv/fs/file/own/2">Eigene.docx</a></form>`;
  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);
});