import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { parseLessonTaskLinks, withScrapedIds } from '../src/core/lesson-page.ts'; /** * Fixtures mirror the legacy topic page as actually served — the task cards it * renders under `
`, which is the only place a topic-attached * task's id is exposed at all. */ const taskCard = (id: string, name: string) => `
  • ` + `
    Fällig: 30.07.2026 15:23
  • `; const page = (cards: string) => `

    Aufgaben (2)

    ` + `
      ${cards}
    ` + ``; const id = (c: string) => c.repeat(24); describe('parseLessonTaskLinks', () => { it('recovers the ids the API withholds, with their names', () => { const html = page(taskCard(id('a'), 'Bestandteile einer Kamera') + taskCard(id('b'), 'Eigenschaften einer Sammellinse')); assert.deepEqual(parseLessonTaskLinks(html), [ { id: id('a'), name: 'Bestandteile einer Kamera' }, { id: id('b'), name: 'Eigenschaften einer Sammellinse' }, ]); }); it('decodes entities in task names', () => { const html = page(taskCard(id('c'), 'Größe & Form')); assert.equal(parseLessonTaskLinks(html)[0]?.name, 'Größe & Form'); }); it('reports each task once even when the page links it twice', () => { // The card title and its action button both point at the same task. const html = page(taskCard(id('d'), 'Würfelspiel') + taskCard(id('d'), 'Würfelspiel')); assert.equal(parseLessonTaskLinks(html).length, 1); }); it('returns nothing rather than guessing when the markup is not a topic page', () => { assert.deepEqual(parseLessonTaskLinks('Anmelden'), []); }); it('ignores homework links that carry no task name', () => { // A bare link with no aria-label cannot be paired with an API task, so it // is no use; taking it would produce a task named after nothing. assert.deepEqual(parseLessonTaskLinks(page(`Aufgabe`)), []); }); }); describe('withScrapedIds', () => { it('gives the API tasks the ids from the page, matching on name', () => { const tasks = [{ name: 'Würfelspiel' }, { name: 'Sammellinse' }]; const links = [ { id: id('a'), name: 'Sammellinse' }, { id: id('b'), name: 'Würfelspiel' }, ]; assert.deepEqual( withScrapedIds(tasks, links).map((t) => t.id), [id('b'), id('a')], ); }); it('leaves a task the page does not account for without an id', () => { // Still worth reporting — the user can see it — but the id-taking tools // cannot open it, so it must not be handed a fabricated id. const merged = withScrapedIds([{ name: 'Nur in der API' }], [{ id: id('a'), name: 'Etwas anderes' }]); assert.equal(merged[0]?.id, undefined); assert.equal(merged[0]?.name, 'Nur in der API'); }); it('does not overwrite an id the task already has', () => { const merged = withScrapedIds([{ id: id('f'), name: 'Würfelspiel' }], [{ id: id('a'), name: 'Würfelspiel' }]); assert.equal(merged[0]?.id, id('f')); }); it('tolerates surrounding whitespace in the API name', () => { const merged = withScrapedIds([{ name: ' Würfelspiel ' }], [{ id: id('a'), name: 'Würfelspiel' }]); assert.equal(merged[0]?.id, id('a')); }); });