import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { daysUntil, formatDate, htmlToText, joinSections, normalizeObjectId } from '../src/core/text.ts'; describe('htmlToText', () => { it('unwraps the CKEditor markup Schulcloud stores', () => { assert.equal(htmlToText('
Hallo Welt
'), 'Hallo Welt'); }); it('keeps the href when the link text differs from it', () => { assert.equal( htmlToText(''), 'Beispiel (https://example.org/x)', ); }); it('does not duplicate a bare URL used as its own label', () => { assert.equal(htmlToText('https://example.org'), 'https://example.org'); }); it('renders list items as bullets and collapses blank runs', () => { assert.equal(htmlToText('a < b < c d
'), 'a < b < c d'); }); it('returns an empty string for missing input', () => { assert.equal(htmlToText(undefined), ''); assert.equal(htmlToText(null), ''); }); }); describe('formatDate', () => { it('renders ISO timestamps as minute-precision UTC', () => { assert.equal(formatDate('2026-08-17T08:00:00.000Z'), '2026-08-17 08:00'); }); it('passes through unparseable values rather than printing Invalid Date', () => { assert.equal(formatDate('not a date'), 'not a date'); }); it('marks absent dates', () => { assert.equal(formatDate(null), '—'); }); }); describe('daysUntil', () => { it('is negative for past dates and undefined when unset', () => { const yesterday = new Date(Date.now() - 86_400_000).toISOString(); assert.ok((daysUntil(yesterday) ?? 0) < 0); assert.equal(daysUntil(undefined), undefined); }); }); describe('joinSections', () => { it('drops empty and falsy parts', () => { assert.equal(joinSections(['a', '', undefined, false, ' ', 'b']), 'a\n\nb'); }); }); describe('normalizeObjectId', () => { it('converts the buffer shape the legacy lesson API returns', () => { const id = { buffer: { type: 'Buffer', data: [106, 130, 219, 101, 127, 25, 207, 115, 254, 60, 242, 13] } }; assert.equal(normalizeObjectId(id), '6a82db657f19cf73fe3cf20d'); }); it('passes plain strings through and gives up on anything else', () => { assert.equal(normalizeObjectId('abc'), 'abc'); assert.equal(normalizeObjectId({}), undefined); }); }); describe('decodeEntities', () => { it('decodes German umlauts, which the legacy pages emit as named entities', () => { assert.equal(htmlToText('vollständig und nachvollziehbar
'), 'vollständig und nachvollziehbar'); assert.equal(htmlToText('Größe, Übung
'), 'Größe, Übung'); }); it('decodes decimal and hex numeric references', () => { assert.equal(htmlToText('€ € ä
'), '€ € ä'); }); it('does not double-decode: ä stays literal text', () => { assert.equal(htmlToText('ä
'), 'ä'); }); it('leaves unknown entities alone rather than mangling them', () => { assert.equal(htmlToText('¬arealentity; &
'), '¬arealentity; &'); }); it('ignores out-of-range numeric references', () => { assert.equal(htmlToText(''), ''); }); });