Once a submission's due date passes, the legacy homework page renders the student's text read-only in a `<div class="comment">` that is a sibling *after* `</section id="submission">`, which then holds only the file list. `parseHomeworkPage` searched for that div *inside* the submission section, so for every past-due submission it silently returned no submitted text while still reporting the grade and feedback — the reader would conclude the student handed in nothing. Search the whole page instead. `class="comment"` (quote right after the word) stays specific: the teacher's feedback is `class="comment ckcontent"` and does not match, and the editable-textarea branch is still tried first. Found by standing up a local instance from the deployed images and reading a real 33.40 page for a seeded past-due submission; the earlier test fixture had nested the div inside the section, which is why the gap was invisible. The fixtures now match the real DOM. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
95 lines
4.3 KiB
TypeScript
95 lines
4.3 KiB
TypeScript
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 }) => `
|
|
<html><body>
|
|
<nav class="nav tab-links"><a href="#activetabid=extended">Details</a></nav>
|
|
<section id="extended" class="tab-content">keine Beschreibung vorhanden</section>
|
|
<section id="submission" class="tab-content">${parts.submission ?? ''}</section>
|
|
${parts.afterSubmission ?? ''}
|
|
${parts.feedback === undefined ? '' : `<section id="feedback" class="tab-content">${parts.feedback}</section>`}
|
|
</body></html>`;
|
|
|
|
const fileCard = (id: string, name: string, size = 1234) =>
|
|
`<div class="card file " data-file-name="${name}" data-file-size="${size}" data-file-id="${id}"></div>`;
|
|
|
|
describe('parseHomeworkPage', () => {
|
|
it("reads the teacher's written feedback, which no API exposes", () => {
|
|
const html = page({
|
|
feedback: `<div class="comment ckcontent" data-testid="feedback-comment"><p>vollständig und nachvollziehbar</p></div>`,
|
|
});
|
|
assert.equal(parseHomeworkPage(html)?.gradeComment, 'vollständig und nachvollziehbar');
|
|
});
|
|
|
|
it('reads a percentage grade from the feedback tab', () => {
|
|
const html = page({ feedback: '<p>Du hast 85% erreicht</p>' });
|
|
assert.equal(parseHomeworkPage(html)?.gradePercent, 85);
|
|
});
|
|
|
|
it('reads the student\'s typed answer from the editable textarea', () => {
|
|
const html = page({
|
|
submission: `<textarea data-testid="submission-text" name="comment"> <p>Meine Lösung</p> </textarea>`,
|
|
});
|
|
assert.equal(parseHomeworkPage(html)?.submittedText, 'Meine Lösung');
|
|
});
|
|
|
|
it('reads the typed answer from the read-only form once submission closed', () => {
|
|
// The read-only `<div class="comment">` 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: `<section class="files" data-testid="submissions-section-files"></section>`,
|
|
afterSubmission: `<div class="comment"><p>Abgegebener Text</p></div>`,
|
|
});
|
|
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: `<div class="comment"><p>Meine Abgabe</p></div>`,
|
|
feedback: `<div class="comment ckcontent" data-testid="feedback-comment"><p>Gut gemacht</p></div>`,
|
|
});
|
|
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('<html><body>Anmelden</body></html>'), 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');
|
|
});
|
|
});
|