import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { parseHomeworkPage } from '../src/core/homework-page.ts'; /** * Fixtures mirror the legacy client's templates (feedback.hbs, submission.hbs) * as actually served — including the parts that are absent for a student, which * is what makes every field optional. */ const page = (parts: { submission?: string; feedback?: string; afterSubmission?: string }) => `
keine Beschreibung vorhanden
${parts.submission ?? ''}
${parts.afterSubmission ?? ''} ${parts.feedback === undefined ? '' : `
${parts.feedback}
`} `; const fileCard = (id: string, name: string, size = 1234) => `
`; describe('parseHomeworkPage', () => { it("reads the teacher's written feedback, which no API exposes", () => { const html = page({ feedback: `

vollständig und nachvollziehbar

`, }); assert.equal(parseHomeworkPage(html)?.gradeComment, 'vollständig und nachvollziehbar'); }); it('reads a percentage grade from the feedback tab', () => { const html = page({ feedback: '

Du hast 85% erreicht

' }); assert.equal(parseHomeworkPage(html)?.gradePercent, 85); }); it('reads the student\'s typed answer from the editable textarea', () => { const html = page({ submission: ``, }); assert.equal(parseHomeworkPage(html)?.submittedText, 'Meine Lösung'); }); it('reads the typed answer from the read-only form once submission closed', () => { // The read-only `
` is a sibling *after* the closed // submission section, not a child of it — verified against a real 33.40 // page for a past-due submission. A fixture that nested it inside the // section (as an earlier one did) hid a bug where every past-due // submission lost its text. See src/core/homework-page.ts. const html = page({ submission: `
`, afterSubmission: `

Abgegebener Text

`, }); assert.equal(parseHomeworkPage(html)?.submittedText, 'Abgegebener Text'); }); it('does not confuse the submitted text with the teacher feedback comment', () => { // Both are comment divs, distinguished only by the exact class: the // student's is `class="comment"`, the teacher's `class="comment ckcontent"`. const html = page({ afterSubmission: `

Meine Abgabe

`, feedback: `

Gut gemacht

`, }); const detail = parseHomeworkPage(html); assert.equal(detail?.submittedText, 'Meine Abgabe'); assert.equal(detail?.gradeComment, 'Gut gemacht'); }); it('separates submitted files from files the teacher returned', () => { const html = page({ submission: fileCard('a'.repeat(24), 'meine-abgabe.pdf'), feedback: fileCard('b'.repeat(24), 'korrektur.pdf'), }); const detail = parseHomeworkPage(html); assert.deepEqual(detail?.submittedFiles, [{ id: 'a'.repeat(24), name: 'meine-abgabe.pdf' }]); assert.deepEqual(detail?.gradingFiles, [{ id: 'b'.repeat(24), name: 'korrektur.pdf' }]); }); it('returns a result with nothing set when the page carries no submission', () => { // The common case for an ungraded task: sections present but empty. const detail = parseHomeworkPage(page({})); assert.ok(detail); assert.equal(detail.submittedText, undefined); assert.equal(detail.gradeComment, undefined); assert.deepEqual(detail.gradingFiles, []); }); it('gives up rather than guessing when the markup is not a homework page', () => { assert.equal(parseHomeworkPage('Anmelden'), undefined); }); it('decodes entities in file names', () => { const html = page({ submission: fileCard('c'.repeat(24), 'A&B "final".pdf') }); assert.equal(parseHomeworkPage(html)?.submittedFiles[0]?.name, 'A&B "final".pdf'); }); });