Skip to content

Reopen a Text entry answer of NULL as authored - #6285

Merged
AlexVelezLl merged 1 commit into
learningequality:unstablefrom
rtibblesbot:issue-6272-51b237
Oct 7, 2026
Merged

AlexVelezLl merged 1 commit into
learningequality:unstablefrom
rtibblesbot:issue-6272-51b237

Conversation

@rtibblesbot

@rtibblesbot rtibblesbot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Text entry answer NULL reopened empty, marking the question incomplete.
  • Studio no longer reads the QTI value NULL as null, an assumption copied from Kolibri.

References

Fixes #6272.

Reviewer guidance

Legacy check after pnpm devsetup (sign in a@a.com / a):

python contentcuration/manage.py shell --settings=contentcuration.dev_settings -c '
from contentcuration.models import Channel, ContentNode
from contentcuration.utils.db_tools import create_question
tree = Channel.objects.get(name="Published Channel").main_tree.tree_id
node = ContentNode.objects.get(title="Sample Exercise", tree_id=tree)
create_question(node, "Capital of France?", "input_question", [
    {"answer": "Paris", "correct": True, "order": 1},
    {"answer": "paris", "correct": True, "order": 2},
    {"answer": "NULL", "correct": True, "order": 3},
    {"answer": "London", "correct": False, "order": 4},
])
create_question(node, "Describe Paris.", "free_response", [])
'

Open "Sample Exercise": "Capital of France?" is Text entry with Paris/paris/NULL ticked; "Describe Paris." is Free response. Add Lutetia, untick paris, reopen: Paris ☑, paris ☐, NULL ☑, Lutetia ☐.

Manual QA. QA team: A Text entry answer of NULL now reopens as typed. A regression is most probable in reopened answers, their Case-sensitive ticks, their errors and the incomplete-question count.

In an exercise, open Edit details > Questions. Click New question and set Response type to Text entry. To reopen, click Close, reload the page and click Edit on the question.

  1. NULL answer: Add NULL with Case-sensitive on, then off. Reopen. NULL shows with the same tick and no Incomplete badge.
  2. NULL, null and Apple: Add NULL (Case-sensitive on), null (off), Apple (on) and Apple (off). No error shows. Reopen. NULL (on), null (off) and Apple (off) show.
  3. Duplicates: In question 2, click Add acceptable answer and type apple. "Duplicate answers are not allowed" shows under Apple and apple. Reopen. "1 incomplete question" and both errors show. Delete apple and reopen. The count goes away.
  4. Spaces and blanks: In a new Text entry question, add apple and one empty answer. Reopen. apple shows without spaces. The empty answer shows "Cannot be empty".
  5. Numeric: Set Response type to Numeric. Add 5, 5.0 and 1.2.3. Reopen. "Duplicate answers are not allowed" shows under 5 and 5.0, and "Must be a valid number (e.g. 12, 0.5, -3.14)" under 1.2.3. Incomplete shows. Change them to 5.50 and 1.20, then reopen. The answers show as typed, with no errors.

Older input questions cannot be made in the UI. The reviewer check above covers them.

Evidence

NULL, null and Apple

Step Screenshot
Authored: NULL on, null off, Apple on, Apple off; no error Authored: NULL on, null off, Apple on, Apple off; no error
Reopened, apple added: duplicate error under Apple and apple Reopened, apple added: duplicate error under Apple and apple
Reopened with the duplicate: "1 incomplete question" Reopened with the duplicate: 1 incomplete question
Older input question: NULL, null, Apple listed once each, no error Older input question: NULL, null, Apple listed once each, no error
Older input question: null unticked, reopened; NULL still ticked Older input question: null unticked, reopened; NULL still ticked

Numeric answers

Step Screenshot
Reopened: 5, 5.0 duplicate and 1.2.3 invalid; Incomplete Reopened: 5, 5.0 duplicate and 1.2.3 invalid; Incomplete
Fixed and reopened: 5, 5.50, 1.20, no errors Fixed and reopened: 5, 5.50, 1.20, no errors
More captures (3)
Step Screenshot
Older input questions in the list Older input questions in the list
Numeric errors before save Numeric errors before save
Numeric fixed before save Numeric fixed before save

AI usage

Claude Code wrote the fix and tests.

🤖 Generated with Claude Code


Written by rtibblesbot, an LLM-based coding agent.

@rtibblesbot

🟡 Waiting for feedback

Last updated: 2026-10-06 23:09 UTC

@rtibblesbot
rtibblesbot marked this pull request as ready for review October 4, 2026 14:38
@rtibblesbot
rtibblesbot requested review from a team and leq-bot October 4, 2026 14:38
@rtibbles rtibbles self-assigned this Oct 4, 2026

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase.

@rtibbles rtibbles assigned AlexVelezLl and unassigned rtibbles Oct 5, 2026
@rtibblesbot

rtibblesbot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased onto unstable. Conflict was in textEntry/parse.js with #6265's single-cardinality mapping change: the shared extractAnswerValues helper now keeps its editor-shape rule (repeated map-keys kept when the correct response is the first key), and Text answers with no mapping still read as case-sensitive. QTIEditor Jest suites pass (1357).

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the correct fix. The bug is that we incorrectly treat "NULL" strings as null values in the coerceValue method. This came from the same Kolibri assumption. Could you instead remove that condition wherever it appears in Studio, and open an issue in Kolibri reporting the same thing there?

`coerceValue` mirrored Kolibri's assumption that the string `NULL` is
QTI NULL, so a Text entry answer of `NULL` reopened empty.

Fixes learningequality#6272

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rtibblesbot

Copy link
Copy Markdown
Contributor Author

@AlexVelezLl Done: coerceValue no longer treats 'NULL' as null; the parse-path fix is gone.

  • Searched git grep for 'NULL'/"NULL" across JS, Vue and Python: one code site, QTIDeclaration.coerceValue. Changed it.
  • Two tests pinned the old rule: the coerceValue null-handling case and a float default of NULL in parse.spec.js. Dropped 'NULL' from both; a float default of NULL is now rejected like any other non-number.
  • The branch is one commit, fix: stop reading the QTI value NULL as null. The PR description still describes the old approach.
  • Kolibri issue drafted; it is filed with a link here after this run.

Saved NULL, Case-sensitive on, reopened after reload:

NULL with Case-sensitive on, reopened after reload

The unticked reopen was not captured: the QA server's Redis failed mid-run. Its saved XML reads <qti-map-entry map-key="NULL" mapped-value="1"/>, and validateItem.spec.js covers both ticks.

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@AlexVelezLl
AlexVelezLl merged commit 75c4da9 into learningequality:unstable Oct 7, 2026
13 checks passed
@rtibblesbot

Copy link
Copy Markdown
Contributor Author

Quick check on rtibblesbot's work on this PR: react 👍 helpful · 😕 mixed · 👎 not helpful. Reply with anything specific.

@rtibblesbot
rtibblesbot deleted the issue-6272-51b237 branch October 7, 2026 13:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[QTI] A Text entry answer of NULL reopens empty and makes the question incomplete

3 participants