-
-
Notifications
You must be signed in to change notification settings - Fork 307
Replace the legacy assessment editor with the QTI editor #6095
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
AlexVelezLl
wants to merge
14
commits into
learningequality:unstable
Choose a base branch
from
AlexVelezLl:base-qti-migration
base: unstable
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
dbf43e7
fix: convert the legacy markdown the QTI models rejected
rtibblesbot 38e9332
fix: give an answerless legacy choice question an empty choice intera…
rtibblesbot 9625adf
feat: convert still-legacy assessment items to QTI on read
rtibblesbot 7469452
fix: make QTI serialization match what the schema and API accept
AlexVelezLl 46b10dd
refactor: split the pure interaction registry from the editor components
AlexVelezLl 3e841a8
feat: validate and create QTI items without rendering them
AlexVelezLl 5617297
feat: show each question's state in its card
AlexVelezLl 5549d64
refactor: validate interactions without debouncing
AlexVelezLl 9561ce7
feat: author and preview exercise questions with the QTI editor
AlexVelezLl 7a38bfc
refactor: remove the legacy assessment editor
AlexVelezLl 05e3f9f
fix: read and write converted legacy questions correctly
AlexVelezLl 13ec431
feat: read and edit the hints of a converted legacy question
AlexVelezLl 0f1fe73
fix: treat image-only and formula-only rich text as content
AlexVelezLl d8b5f6d
refactor: use clearer tests names
AlexVelezLl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
...entcuration/frontend/shared/views/QTIEditor/serialization/__tests__/convertedItem.spec.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| /** | ||
| * The editor's read/write pair against what the converter actually emits. | ||
| * | ||
| * These read the converter's own fixtures rather than a copy, because the bug this file | ||
| * exists to catch was a disagreement between the two: `testingFixtures.js` is hand-written | ||
| * and carries no grading declarations, so nothing noticed that a converted item loses its | ||
| * scoring rules on the first save. | ||
| */ | ||
| // Disabled because jest-dom's matchers are built for HTML elements and reject the strict | ||
| // XML nodes this serialization produces — same reason as assembleItem.spec.js. | ||
| /* eslint-disable jest-dom/prefer-to-have-attribute */ | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import { parseItem } from '../parseItem'; | ||
| import { assembleItemXml } from '../assembleItem'; | ||
|
|
||
| const FIXTURES = path.join(__dirname, '../../../../../../tests/utils/qti/fixtures'); | ||
|
|
||
| const read = name => fs.readFileSync(path.join(FIXTURES, `${name}.xml`), 'utf8'); | ||
|
|
||
| const rebuild = item => | ||
| assembleItemXml({ | ||
| identifier: item.identifier, | ||
| title: item.title, | ||
| language: item.language, | ||
| bodyXml: item.interactions[0].bodyXml, | ||
| responseDeclarations: item.interactions[0].responseDeclarations, | ||
| hints: item.hints, | ||
| }); | ||
|
|
||
| describe('a converted single-selection item', () => { | ||
| const original = read('single_selection'); | ||
|
|
||
| it('is read with the language the converter wrote', () => { | ||
| expect(parseItem(original).language).toBe('en-US'); | ||
| }); | ||
|
|
||
| it('writes the language back as xml:lang, the attribute QTI declares', () => { | ||
| const xml = rebuild(parseItem(original)); | ||
| expect(xml).toContain('xml:lang="en-US"'); | ||
| expect(xml).not.toContain(' language="'); | ||
| }); | ||
|
|
||
| it('keeps its scoring outcome and response processing', () => { | ||
| const xml = rebuild(parseItem(original)); | ||
| const doc = new DOMParser().parseFromString(xml, 'text/xml'); | ||
| expect(doc.querySelector('parsererror')).toBeNull(); | ||
| expect(doc.querySelector('qti-outcome-declaration').getAttribute('identifier')).toBe('SCORE'); | ||
| expect(doc.querySelector('qti-response-processing').getAttribute('template')).toBe( | ||
| 'https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/match_correct', | ||
| ); | ||
| }); | ||
|
|
||
| it('puts the children in the order the schema fixes', () => { | ||
| const xml = rebuild(parseItem(original)); | ||
| const order = [...xml.matchAll(/<(qti-[a-z-]+)/g)] | ||
| .map(m => m[1]) | ||
| .filter(tag => | ||
| [ | ||
| 'qti-response-declaration', | ||
| 'qti-outcome-declaration', | ||
| 'qti-item-body', | ||
| 'qti-response-processing', | ||
| ].includes(tag), | ||
| ); | ||
| expect(order).toEqual([ | ||
| 'qti-response-declaration', | ||
| 'qti-outcome-declaration', | ||
| 'qti-item-body', | ||
| 'qti-response-processing', | ||
| ]); | ||
| }); | ||
| }); | ||
|
|
||
| describe('an item this editor wrote', () => { | ||
| it('keeps xml:lang, the spelling this editor uses', () => { | ||
| const xml = assembleItemXml({ | ||
| identifier: 'i', | ||
| title: 't', | ||
| language: 'es', | ||
| bodyXml: '<qti-choice-interaction response-identifier="RESPONSE"/>', | ||
| responseDeclarations: ['<qti-response-declaration identifier="RESPONSE"/>'], | ||
| }); | ||
| expect(xml).toContain('xml:lang="es"'); | ||
| }); | ||
|
|
||
| it('omits the language rather than inventing one', () => { | ||
| const xml = assembleItemXml({ | ||
| identifier: 'i', | ||
| title: 't', | ||
| language: '', | ||
| bodyXml: '<qti-choice-interaction response-identifier="RESPONSE"/>', | ||
| responseDeclarations: [], | ||
| }); | ||
| expect(xml).not.toContain('xml:lang'); | ||
| expect(xml).not.toContain('language='); | ||
| }); | ||
|
|
||
| it('scores nothing when there is nothing to answer', () => { | ||
| const xml = assembleItemXml({ | ||
| identifier: 'i', | ||
| title: 't', | ||
| language: 'en', | ||
| bodyXml: '<qti-item-body><p>Just text.</p></qti-item-body>', | ||
| responseDeclarations: [], | ||
| }); | ||
| expect(xml).toContain('qti-outcome-declaration'); | ||
| expect(xml).not.toContain('qti-response-processing'); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.