Skip to content

[QTI] Editing a question drops content the editor doesn't model #6309

Open

Description

@rtibblesbot

❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Target branch: unstable

Observed behavior

Saving any edit to a QTI question rewrites parts the editor doesn't model. isEditableItem (validateItem.js#L45-L127) checks only the root's attributes and children, so none of these open read-only:

  • Interaction attributes: the builders write only what they set (textEntry/parse.js#L308-L320, choice/parse.js#L139-L145). Every converted legacy input question loses placeholder-text="Enter your answer here"; a choice loses class="qti-labels-upper-alpha".
  • Prefixed MathML in the prompt: adoptNode recreates <m:math> with its prefix (xml.js#L99-L103), which TipTap doesn't read as math. solve <m:math>…</m:math>. saves as <p>solve x+1.</p>.
  • data-catalog-idref in the prompt: TipTap drops the span. Pick the <span data-catalog-idref="g1">vowel</span>. saves as <p>Pick the vowel.</p>. Since Show QTI questions read-only when an edit would drop item-level content #6306 an item that also holds the glossary <qti-catalog> opens read-only; one without it is still editable.
  • Text entry answers: _extractAnswers compares the trimmed <qti-value> with the untrimmed first map-key (textEntry/parse.js#L214-L224), so it reads answers by value. Map-keys Paris, Paris (case-sensitive), Paris open as Paris, Paris.

Expected behavior

  • An edit keeps everything it didn't touch.

User-facing consequences

  • Learners see a question that differs from the uploaded one: no placeholder, no choice labels, no formula, no glossary term, or a dropped answer.
  • The author isn't told.

Steps to reproduce

  1. Open a converted legacy input question (input_question.xml), click Edit, change a hint, click Close: <qti-text-entry-interaction> has no placeholder-text.
  2. Open a single-choice item whose root declares xmlns:m="http://www.w3.org/1998/Math/MathML" and whose prompt holds <m:math>…</m:math>, type at the end of the question text, click Close: the saved prompt has no <m:math>.
  3. Open a single-choice item whose prompt is Pick the <span data-catalog-idref="g1">vowel</span>. and holds no <qti-catalog>, add a hint, click Close: the saved prompt is <p>Pick the vowel.</p>.
  4. Call _extractAnswers on the declaration below: it returns [['Paris', false], [' Paris', false]].
    <qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="string">
      <qti-correct-response><qti-value> Paris</qti-value></qti-correct-response>
      <qti-mapping default-value="0">
        <qti-map-entry map-key=" Paris" mapped-value="1" case-sensitive="false"/>
        <qti-map-entry map-key="Paris" mapped-value="1" case-sensitive="true"/>
        <qti-map-entry map-key="Paris" mapped-value="1" case-sensitive="false"/>
      </qti-mapping>
    </qti-response-declaration>

Acceptance Criteria

  • Editing a converted text entry question keeps placeholder-text; editing a choice question keeps class on <qti-choice-interaction>.
  • Editing the prompt of an item with prefixed MathML saves the formula as MathML, and the item passes the XSD check.
  • An item with data-catalog-idref in its prompt keeps it on save, or opens read-only.
  • The declaration above reads as [[' Paris', false], ['Paris', true], ['Paris', false]], and opening that item emits no update:rawData.
  • Tests cover each case from opening the item to the emitted raw_data.

Context

unstable at 98b560e.

AI usage

rtibbles chose which issues to merge; Claude Code reproduced each case on unstable with Jest and drafted this issue, which merges #6287, #6307 and #6308.

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions