Scrape submitted text for past-due submissions, not just editable ones
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>
This commit is contained in:
@@ -73,9 +73,17 @@ export function parseHomeworkPage(html: string): SubmissionDetail | undefined {
|
||||
|
||||
// The student's own text: a textarea while the submission is still editable,
|
||||
// a plain div once it is not.
|
||||
//
|
||||
// The read-only div is a *sibling after* `</section id="submission">`, not a
|
||||
// child of it — that section then holds only the file list. Scoping this
|
||||
// search to the section therefore found nothing for every submission past
|
||||
// its due date, silently dropping the submitted text while still reporting
|
||||
// the grade. `class="comment"` (with the quote right after the word) is
|
||||
// specific enough to search the whole page: the teacher's feedback is
|
||||
// `class="comment ckcontent"` and so cannot match.
|
||||
const typed =
|
||||
/data-testid="submission-text"[^>]*>([\s\S]*?)<\/textarea>/.exec(html)?.[1] ??
|
||||
(submission ? /<div class="comment"[^>]*>([\s\S]*?)<\/div>/.exec(submission)?.[1] : undefined);
|
||||
/<div class="comment"[^>]*>([\s\S]*?)<\/div>/.exec(html)?.[1];
|
||||
const typedText = clean(typed);
|
||||
if (typedText) detail.submittedText = typedText;
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ import { parseHomeworkPage } from '../src/core/homework-page.ts';
|
||||
* 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 }) => `
|
||||
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>`;
|
||||
|
||||
@@ -39,10 +40,30 @@ describe('parseHomeworkPage', () => {
|
||||
});
|
||||
|
||||
it('reads the typed answer from the read-only form once submission closed', () => {
|
||||
const html = page({ submission: `<div class="comment"><p>Abgegebener Text</p></div>` });
|
||||
// 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'),
|
||||
|
||||
Reference in New Issue
Block a user