Validate QTI numeric answers the same way inline and headlessly - #6258
Conversation
640b7be to
11627c0
Compare
|
|
||
| if (val) { | ||
| if (seen.has(lookupKey)) { | ||
| errors.push({ code: ValidationError.DUPLICATE_ANSWER_CONTENT, id: answer.id }); |
There was a problem hiding this comment.
Could you fix the error here that only flags the latter value as a duplicate instead of flagging all of them?
There was a problem hiding this comment.
Fixed in c820430: every answer in a repeated group is now flagged, matching choice/validation.js.
- Searched every
DUPLICATE_*producer underQTIEditor/interactions/: 5 matched. Choice and ordering already flag all copies by id; associate and match flag by text. Text entry was the only one flagging just the later copy. - Tests now expect both ids (numeric and text entry), plus the headless
validateItemcases.
| checked.querySelectorAll(':scope > qti-correct-response, :scope > qti-mapping').forEach(el => { | ||
| el.remove(); | ||
| }); | ||
| QTIDeclaration.fromXML(checked); |
There was a problem hiding this comment.
It's very difficult to understand what this is doing, and I'm not completely sure yet. Could you add more comments, please? Isn't there a better way to do it? Given that the QTIDeclaration.fromXML response isn't being stored anywhere, it makes me think this is rather asserting something indirectly, which is why the intention here is not clear.
There was a problem hiding this comment.
It asserts that the declaration parses. The answers are left out because fromXML coerces floats, which would throw on or truncate an invalid answer. The rest is still checked: identifier, cardinality and default value. A malformed float declaration then yields [], as on the string path.
Moved it into a named helper, assertDeclarationParses, with a docstring saying this (54f1f81).
Constructing QTIDeclaration from the attributes alone would be more direct. It would skip the default-value check, though, and the validateItem test "reports a non-numeric default value as no correct answer" fails without it. The other way is a raw-read mode in QTIDeclaration, which would change shared serialization code for one caller.
There was a problem hiding this comment.
Replaced the helper with the direct checks in 8baa10b: extractNumericAnswers constructs QTIDeclaration from the attributes (identifier, cardinality), then rejects a non-numeric default value explicitly. No fromXML call on the float path now.
There was a problem hiding this comment.
Now in 2b0f97a: the default value goes through QTIDeclaration.coerceValue(…, BaseType.FLOAT), the same check fromXML applies, so a default like 1,234 that Kolibri accepts no longer drops the answers.
11627c0 to
c820430
Compare
- Replace the loose regex, which accepted `e`, `-`, `1e2e3` and rejected `1e-5`, `1E5` - Compare numeric answers by value, so `21` and `21.0` are duplicates Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
c820430 to
e26c7ea
Compare
Float coercion dropped every answer on an invalid value and merged `21` with `21.0`, so headless validation reported an incomplete question the editor showed no error for. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
e26c7ea to
2354019
Compare
|
Outside this PR's scope — tracked under #6103:
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly How was this generated?
|
AlexVelezLl
left a comment
There was a problem hiding this comment.
Code changes make sense, and manual QA checks out! LGTM!
Summary
xsd:doubleNumeric answers, compared by value (21duplicates21.0).References
Fixes #6150. First seen: #6095 (comment)
Reviewer guidance
21.0), not normalized. Reformat on load?QA steps
Setup:
pnpm devsetup, admin login. New question: "Published Channel" → "Topic 2" → "Add" → "New exercise" → "Questions" → "New question", plus a prompt. QTI demo:/channels/<channel-id>/#/qti-demo.Evidence
Numeric repeated answers
numeric-duplicates-flow.webm
Text entry repeated answers
More captures (4)
AI usage
Claude Code wrote the fix and tests; verified with Jest and pre-commit.
Deviations from the issue spec
xsd:double1e400overflows toInfinityand is rejected likeINF@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
🟡 Waiting for feedback
Last updated: 2026-10-01 22:02 UTC