diff --git a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/EditableChipList/index.vue b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/EditableChipList/index.vue index aadc8ff03b..ea7af394ce 100644 --- a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/EditableChipList/index.vue +++ b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/components/EditableChipList/index.vue @@ -167,9 +167,9 @@ import ClickableRegion from '../ClickableRegion/index.vue'; import ValidationMessage from '../ValidationMessage/index.vue'; import { qtiEditorStrings } from '../../qtiEditorStrings'; - import { hasRichTextContent } from '../../utils/richText'; import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor'; import EditorImageProcessor from 'shared/views/TipTapEditor/TipTapEditor/services/imageService'; + import { hasRichTextContent } from 'shared/views/TipTapEditor/TipTapEditor/utils/comparisonKey'; // Holds a message line open without text const NO_BREAK_SPACE = '\u00a0'; diff --git a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/composables/useInlineChoiceInteraction.js b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/composables/useInlineChoiceInteraction.js index 7dd01f1227..477e600e28 100644 --- a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/composables/useInlineChoiceInteraction.js +++ b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/composables/useInlineChoiceInteraction.js @@ -1,8 +1,8 @@ import { computed, readonly } from 'vue'; import { inlineChoiceInteractionDescriptor } from '../interactions/inlineChoice/Descriptor'; import { getDropdowns } from '../interactions/inlineChoice/parse'; -import { hasRichTextContent } from '../utils/richText'; import { useInteraction } from './useInteraction'; +import { hasRichTextContent } from 'shared/views/TipTapEditor/TipTapEditor/utils/comparisonKey'; /** * Composable for the inline choice interaction editor. diff --git a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/Editor.vue b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/Editor.vue index 174e989a49..bae7fd89db 100644 --- a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/Editor.vue +++ b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/Editor.vue @@ -248,9 +248,12 @@ import ClickableRegion from '../../components/ClickableRegion/index.vue'; import ShuffledResponsePool from '../../components/ShuffledResponsePool/index.vue'; import EditableChipList from '../../components/EditableChipList/index.vue'; - import { hasRichTextContent } from '../../utils/richText'; + import { resolveAssociateChoices } from './parse'; import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor'; - import { richTextComparisonKey } from 'shared/views/TipTapEditor/TipTapEditor/utils/comparisonKey'; + import { + hasRichTextContent, + richTextComparisonKey, + } from 'shared/views/TipTapEditor/TipTapEditor/utils/comparisonKey'; import EditorImageProcessor from 'shared/views/TipTapEditor/TipTapEditor/services/imageService'; /** Which card currently holds the open TipTap editor. */ @@ -531,27 +534,21 @@ props.mode === 'edit' ? correctPairsLabel$() : correctAnswersLabel$(), ); - // Blank pair members are left out: every blank choice keys the same, so - // keeping them would mark an unwritten option in the pool as correct. - const pairedTexts = computed( - () => - new Set( - flatten(state.value.pairs) - .filter(choice => hasRichTextContent(choice.content)) - .map(choice => richTextComparisonKey(choice.content)), - ), - ); - - // One option per association the learner can make: the emitted XML folds - // choices holding the same answer into a single option whose match-max is - // the number of occurrences walked here. - const poolChoices = computed(() => - [...flatten(state.value.pairs), ...state.value.distractors].map(choice => ({ - content: choice.content, - isCorrect: - props.showAnswers && pairedTexts.value.has(richTextComparisonKey(choice.content)), - })), - ); + // Kolibri's pool shows each choice of the interaction once. + const poolChoices = computed(() => { + const { pairs, pool } = resolveAssociateChoices(state.value.pairs, state.value.distractors); + // Blank pair members are left out: every blank choice keys the same, so + // keeping them would mark an unwritten option in the pool as correct. + const pairedKeys = new Set( + flatten(pairs) + .map(({ key }) => key) + .filter(Boolean), + ); + return pool.map(({ content, key }) => ({ + content, + isCorrect: props.showAnswers && pairedKeys.has(key), + })); + }); return { EditorImageProcessor, diff --git a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/__tests__/Editor.spec.js b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/__tests__/Editor.spec.js index fc5e8dfdb9..c85198d4c9 100644 --- a/contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/__tests__/Editor.spec.js +++ b/contentcuration/contentcuration/frontend/shared/views/QTIEditor/interactions/associate/__tests__/Editor.spec.js @@ -11,6 +11,7 @@ import { mockInteractionBlockWithDecl as blockWithDecl, } from '../../../utils/testingFixtures'; import { QuestionType } from '../../../constants'; +import { parseXML } from '../../../serialization/xml'; import { qtiEditorStrings as tr } from '../../../qtiEditorStrings'; jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor'); @@ -60,6 +61,37 @@ const SHARED_CHOICE_DECL_XML = ` `; +const PAIRED_DISTRACTOR_XML = ` +

Match each character to his adversary.

+ Antonio + Prospero + Antonio +
`; + +const BLANK_CHOICES_XML = ` +

Match each character to his adversary.

+ + Prospero + Capulet + +
`; + +const THRICE_SHARED_CHOICE_XML = ` +

Match each character to his adversary.

+ Antonio + Prospero + Capulet + Montague +
`; + +const THRICE_SHARED_CHOICE_DECL_XML = ` + + choice_aaa11111 choice_bbb22222 + choice_aaa11111 choice_ccc33333 + choice_aaa11111 choice_ddd44444 + +`; + // The mock TipTapEditor renders a