Show the unsupported message for QTI items with multiple or unrecognized interactions - #6218
Conversation
3089cef to
cbe7247
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?
|
15fecdc to
dfe73bf
Compare
b7b05db to
e655cc1
Compare
| it('does not block an extended-text item on a consumer that scores its questions', () => { | ||
| expect(validateQtiItem(EXTENDED_TEXT_ITEM_DOCUMENT, { allowFreeResponse: false })).toEqual([]); | ||
| }); |
There was a problem hiding this comment.
"extended-text item on a consumer that scores its questions" is not clear, and it's not clear why the allowFreeResponse is relevant for this test.
There was a problem hiding this comment.
Removed that test; its allowFreeResponse case now runs inside the read-only-item table (allowFreeResponse true and false), so no extended-text fixture is needed. Searched the QTIEditor specs for other tests tied to a specific interaction: resolveDescriptor.spec, parseItem.spec and QTIItemEditor.spec had the same assumption (3 places), all changed.
|
|
||
| it.each([ | ||
| ['several interactions', MULTI_INTERACTION_ITEM_DOCUMENT], | ||
| ['an interaction with no descriptor', UNRECOGNIZED_INTERACTION_ITEM_DOCUMENT], |
There was a problem hiding this comment.
Could we remove the assumption that the hotspot is an interaction with no descriptor? If, in the future, we implement it and have a descriptor, and the fixture is valid, we will also get an empty array, so this is not correct. We can iterate through the registry and pick an interaction not described by any descriptor's type field, and that's it.
There was a problem hiding this comment.
Added UNDESCRIBED_INTERACTION to the fixtures: the first QtiInteraction no registered descriptor's type matches. The unrecognized-item fixture is built from it, and the hotspot/extended-text literals are gone from validateItem.spec, resolveDescriptor.spec, parseItem.spec and QTIItemEditor.spec (the extended-text fixture is deleted).
| import { isSupportedItem } from '../../interactions/resolveDescriptor'; | ||
| import InteractionSection from '../InteractionSection/index.vue'; | ||
| import HintsSection from '../HintsSection/index.vue'; | ||
| import { getAssessmentItemErrors } from 'shared/utils/validation'; |
There was a problem hiding this comment.
No, the QTIEditor component should be extractable and must not import anything from Studio, find a better way.
There was a problem hiding this comment.
Dropped the shared/utils/validation import. The card now calls the QTIEditor's own validateQtiItem for unsupported QTI. Grepped the non-test QTIEditor sources for other shared/ imports: none other than the existing shared/views/TipTapEditor/KDS ones, which I left alone.
There was a problem hiding this comment.
Correction: the grep found other shared/ imports (i18n, strings, TipTapEditor, dragSort) in files this PR doesn't touch. I left those as is; only the import this PR added is removed.
| responseDeclarations: currentResponseDeclarations, | ||
| }, | ||
| ); | ||
| const { interactions, hints, parseError, rawData } = useQtiItem(props.item.raw_data, { |
There was a problem hiding this comment.
If we don't use the itemBodyXml anymore, let's also drop it from the useQtiItem composable.
There was a problem hiding this comment.
Dropped itemBodyXml from useQtiItem (state, assignment, JSDoc). parseItem still returns it; convertedItem.spec reads it from there. No other consumers.
| <!-- .stop: expanding or collapsing the hints must not open the card --> | ||
| <HintsSection | ||
| v-if="hasHints && (mode === 'edit' || showAnswers)" | ||
| v-if="hasHints && !isUnsupported && (mode === 'edit' || showAnswers)" |
There was a problem hiding this comment.
This is becoming a bit complex; could we have a computed property instead and tweak the conditions so that they are more readable?
There was a problem hiding this comment.
Split into computeds: isQti, isEditableQti (readable and blank-or-single-known-interaction), and isUnsupported = !isQti || !isEditableQti. isIncomplete reuses isQti.
There was a problem hiding this comment.
The hints v-if is now a showHints computed (hasHints && !isUnsupported && (mode === 'edit' || showAnswers)). Searched the template conditions this PR adds or changes for other compound expressions: no other match.
2cba6aa to
12d0bb5
Compare
| return false; | ||
| } | ||
| const [{ bodyXml, responseDeclarations }] = interactions; | ||
| return resolveDescriptor(bodyXml, responseDeclarations).descriptor !== null; |
There was a problem hiding this comment.
blocking: This counts an interaction as supported when its descriptor resolved but resolveDescriptor also returned an error, which is what happens for a match with three sets (getQuestionType throws, and the catch keeps the descriptor). The card and node-level validation then disagree on that item:
- Card:
isEditableQtiis true, so it doesn't take the unsupported branch.InteractionSectionshows "could not be loaded", mounts no editor and emits no errors, andcurrentQuestionTypestays null.isIncompleteis therefore false. - Node:
getAssessmentItemErrors→validateQtiItem→isSupportedItemtrue → the loop pushesPARSE_ERROR, so the item counts as invalid.
I confirmed this by rendering the card for every fixture case, with allowFreeResponse true and false, and comparing the badge with getAssessmentItemErrors: 22/24 cases match, and the two mismatches are the three-set match. Two more consequences: the incomplete-questions count includes an item whose card shows no badge, and canOpen is true, so Edit opens a card that only shows a parse error.
This predates the PR (on unstable, the unparseable-XML case also disagreed, and this PR fixes that one), but the new rule says unreadable QTI blocks publishing, and this item doesn't block it on the card.
There was a problem hiding this comment.
isSupportedItem now also requires resolveDescriptor to return no error, so a three-set match is unsupported on the card (read-only, incomplete badge via validateQtiItem) and on the node, which agree. canOpen is false for it too. The resolveDescriptor.spec case that accepted it now rejects it, and QTIItemEditor.spec has a card test for the badge. Searched the callers of isSupportedItem and resolveDescriptor: 2 callers of isSupportedItem (card, validateQtiItem), both go through the changed function; useInteractionDescriptor only runs for supported items. Nothing else needed changing.
f91fd1e to
544f873
Compare
|
Pushed two behaviour changes from self-review:
No screenshot: the browser driver ( |
544f873 to
d67711f
Compare
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add isSupportedItem: exactly one interaction block whose descriptor has an editor and reads its question type without error. Text entry counts per interaction, since the editor holds one blank per item. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…items The card renders no editable controls, including hints, and never rewrites the item's XML. Unreadable or empty QTI still blocks publishing. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
d67711f to
682c657
Compare
Closes #6105
Summary
QTI items without exactly one editable interaction show
unsupportedItemMessage$()in a read-only card;raw_datais never rewritten.QtiInteractionlists every QTI 3.0 interaction.resolveDescriptorreturnsdescriptor: nullon no match;DEFAULT_INTERACTIONis removed.Reviewer guidance
raw_data: content placeholder.pnpm devsetup,pnpm devserver; seedutils/testingFixtures.jsvia the browser data layer.mode="edit"inQTIDemoPage.vue: unsupported items show only CLOSE.QA steps
/en/channels/<channel id>/#/qti-demo), two-interaction and extended-text fixtures, Show answers ticked: both read "This question cannot be edited here"; Edit disabled.Evidence
Switching and editing each editable type
More captures (26)
🤖 Generated with Claude Code
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
🟡 Waiting for feedback
Last updated: 2026-10-02 06:49 UTC