diff --git a/projects/questionset-editor-library-wc/src/styles.scss b/projects/questionset-editor-library-wc/src/styles.scss
index 7dea030d..79b9b530 100644
--- a/projects/questionset-editor-library-wc/src/styles.scss
+++ b/projects/questionset-editor-library-wc/src/styles.scss
@@ -18,4 +18,5 @@
@import "./../../questionset-editor-library/src/lib/components/slider/slider.component.scss";
@import "./../../questionset-editor-library/src/lib/components/template/template.component.scss";
@import "./../../questionset-editor-library/src/lib/components/translations/translations.component.scss";
+@import "./../../questionset-editor-library/src/lib/components/match/match.component.scss";
diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.html b/projects/questionset-editor-library/src/lib/components/match/match.component.html
new file mode 100644
index 00000000..ce10ec67
--- /dev/null
+++ b/projects/questionset-editor-library/src/lib/components/match/match.component.html
@@ -0,0 +1,81 @@
+
Match the following with appropriate answer?
", + options: [ + { + left: "Lotus
", + right: "Flower
", + }, + { + left: "Mango
", + right: "Fruit
", + }, + ], + templateId: "mtf-vertical", + correctMatchPair: [{ "0": 0 }, { "1": 1 }], + numberOfOptions: 4, + }, + prepareMtfBody: { + templateId: "mtf-horizontal", + name: "Match The Following Question", + responseDeclaration: { + response1: { + cardinality: "multiple", + type: "map", + correctResponse: { + value: [ + { + "0": 0, + }, + { + "1": 1, + }, + ], + }, + mapping: [ + { + value: { + "0": 0, + }, + score: 2, + }, + { + value: { + "1": 1, + }, + score: 2, + }, + ], + }, + }, + interactionTypes: ["match"], + interactions: { + response1: { + type: "match", + options: { + left: [ + { + label: "Lotus
", + value: 0, + }, + { + label: "Mango
", + value: 1, + }, + ], + right: [ + { + label: "Flower
", + value: 0, + }, + { + label: "Fruit
", + value: 1, + }, + ], + }, + }, + }, + editorState: { + options: { + left: [ + { + value: { + body: "Lotus
", + value: 0, + }, + }, + { + value: { + body: "Mango
", + value: 1, + }, + }, + ], + right: [ + { + value: { + body: "Flower
", + value: 0, + }, + }, + { + value: { + body: "Fruit
", + value: 1, + }, + }, + ], + }, + }, + qType: "MTF", + primaryCategory: "Match The Following Question", + }, +}; diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts new file mode 100644 index 00000000..39a32168 --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts @@ -0,0 +1,155 @@ +import { TelemetryInteractDirective } from '../../directives/telemetry-interact/telemetry-interact.directive'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MatchComponent } from './match.component'; +import { mockOptionData } from './match.component.spec.data'; +import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange} from '@angular/core'; +import { ConfigService } from '../../services/config/config.service'; +import { EditorTelemetryService } from '../../services/telemetry/telemetry.service'; + + +describe('MatchComponent', () => { + let component: MatchComponent; + let fixture: ComponentFixtureLeftOption1
", + value: 0, + }, + }, + { + value: { + body: "LeftOption2
", + value: 1, + }, + }, + { + value: { + body: "LeftOption3
", + value: 2, + }, + }, + { + value: { + body: "LeftOption4
", + value: 3, + }, + }, + ], + right: [ + { + value: { + body: "RightOption1
", + value: 0, + }, + }, + { + value: { + body: "RightOption2
", + value: 1, + }, + }, + { + value: { + body: "RightOption3
", + value: 2, + }, + }, + { + value: { + body: "RightOption4
", + value: 3, + }, + }, + ], + }, + question: "MTF Question
", + }, + templateId: "mtf-horizontal", + solutions: {}, + interactions: { + response1: { + type: "match", + options: { + left: [ + { + label: "LeftOption1
", + value: 0, + }, + { + label: "LeftOption2
", + value: 1, + }, + { + label: "LeftOption3
", + value: 2, + }, + { + label: "LeftOption4
", + value: 3, + }, + ], + right: [ + { + label: "RightOption1
", + value: 0, + }, + { + label: "RightOption2
", + value: 1, + }, + { + label: "RightOption3
", + value: 2, + }, + { + label: "RightOption4
", + value: 3, + }, + ], + }, + validation: { + required: "Yes", + }, + }, + }, + name: "MTF Question", + responseDeclaration: { + response1: { + cardinality: "multiple", + type: "map", + correctResponse: { + value: [ + { + "0": 0, + }, + { + "1": 1, + }, + { + "2": 2, + }, + { + "3": 3, + }, + ], + }, + mapping: [ + { + value: { + "0": 0, + }, + score: 1, + }, + { + value: { + "1": 1, + }, + score: 1, + }, + { + value: { + "2": 2, + }, + score: 1, + }, + { + value: { + "3": 3, + }, + score: 1, + }, + ], + }, + }, + outcomeDeclaration: { + maxScore: { + cardinality: "multiple", + type: "integer", + defaultValue: 4, + }, + hint :{ + cardinality: "single", + type: "string", + defaultValue: "70f9a0b2-94c3-4d81-86c0-2082fb10a47b" + } + }, + remarks: { + maxLength: 100, + }, + interactionTypes: ["match"], + qType: "MTF", + primaryCategory: "Match The Following Question", + body: "MTF Question
q
", + options: [ + { + left: "a
", + right: "b
", + }, + { + left: "c
", + right: "d
", + }, + ], + templateId: "mtf-horizontal", + corectMatchPair: [{ "0": 0 }, { "1": 1 }], + numberOfOptions: 2, + interactions: { + response1: { + type: "match", + options: { + left: [ + { + label: "a
", + value: 0, + }, + { + label: "b
", + value: 1, + }, + ], + right: [ + { + label: "c
", + value: 0, + }, + { + label: "d
", + value: 1, + }, + ], + }, + }, + validation: { + required: "Yes", + }, + }, + name: "Match The Following Question", + responseDeclaration: { + response1: { + cardinality: "multiple", + type: "integer", + correctResponse: { + value: [{ "0": 0 }, { "1": 1 }], + }, + mapping: [ + { + value: { + "0": 0, + }, + score: 2, + }, + { + value: { + "1": 1, + }, + score: 2, + }, + ], + }, + }, + interactionTypes: ["match"], + editorState: { + options: { + left: [ + { + value: { + body: "a
", + value: 0, + }, + }, + { + value: { + body: "b
", + value: 1, + }, + }, + ], + right: [ + { + value: { + body: "c
", + value: 0, + }, + }, + { + value: { + body: "d
", + value: 1, + }, + }, + ], + }, + question: "q
", + }, + qType: "MTF", + primaryCategory: "Match The Following Question", +}; + export const RubricData = [ { parent: "do_1134357224765685761203", diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts index e8bcf3e3..a102e1fc 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts @@ -25,6 +25,7 @@ import { BranchingLogic, mockEditorCursor, interactionChoiceEditorState, + interactionMatchEditorState, RubricData, videoSolutionObject, mediaVideoArray @@ -288,7 +289,7 @@ describe("QuestionComponent", () => { component.previewFormData(true); expect(component.initialize).toHaveBeenCalled(); }); - + it("#initialize should call when question page for question mcq api fail", () => { spyOn(component, "initialize").and.callThrough(); component.questionId = "do_11330103476396851218"; @@ -308,7 +309,44 @@ describe("QuestionComponent", () => { component.initialize(); expect(component.initialize).toHaveBeenCalled(); }); - + + xit("#initialize should call when question page for question mtf", () => { + component.initialLeafFormConfig = leafFormConfigMock; + component.leafFormConfig = leafFormConfigMock; + component.questionFormConfig=leafFormConfigMock; + spyOn(component, "initialize").and.callThrough(); + component.questionId = "do_11330103476396851218"; + editorService.parentIdentifier = undefined; + component.questionPrimaryCategory = undefined; + spyOn(editorService, "getToolbarConfig").and.returnValue({ + title: "abcd", + showDialcode: "No", + showPreview: "false", + }); + component.toolbarConfig.showPreview = false; + spyOn(editorService, "fetchCollectionHierarchy").and.callFake(() => { + return of(collectionHierarchyMock); + }); + component.questionId = "do_127"; + component.questionSetHierarchy = collectionHierarchyMock.result.questionset; + spyOn(questionService, "readQuestion").and.returnValue( + of(mockData.mtfQuestionMetaData) + ); + component.questionMetaData = mockData.mtfQuestionMetaData.result.question; + component.questionInteractionType = "match"; + component.scoreMapping = + mockData.mcqQuestionMetaData.result.question.responseDeclaration.response1.mapping; + component.sourcingSettings = sourcingSettingsMock; + component.questionInput.setChildQuestion = false; + component.editorState.solutions = [{ + id: '1', + type: 'vedio' + }] + component.initialize(); + component.previewFormData(true); + expect(component.initialize).toHaveBeenCalled(); + }); + it("#initialize should call when question page for question slider", () => { spyOn(component, "initialize").and.callThrough(); component.questionId = "do_11330103476396851218"; @@ -470,6 +508,54 @@ describe("QuestionComponent", () => { expect(component.setQuestionTitle).toHaveBeenCalled(); }); + xit("#initialize should call when question page for question mtf with interactionTypes", () => { + component.questionSetId = "do_1278"; + spyOn(editorService, "fetchCollectionHierarchy").and.callFake(() => { + return of(collectionHierarchyMock); + }); + editorService.parentIdentifier = undefined; + component.questionId = "do_11330103476396851218"; + component.leafFormConfig = leafFormConfigMock; + spyOn(questionService, "readQuestion").and.returnValue( + of(mockData.mtfQuestionMetaData) + ); + spyOn(component, 'setQuestionTitle').and.callFake(() => {}); + spyOn(component, 'populateFormData').and.callFake(() => {}); + component.leafFormConfig = leafFormConfigMock; + spyOn(component, "initialize").and.callThrough(); + component.initialize(); + expect(component.initialize).toHaveBeenCalled(); + expect(component.questionPrimaryCategory).toBeDefined(); + expect(component.questionInteractionType).toBeDefined(); + expect(component.populateFormData).toHaveBeenCalled(); + expect(component.setQuestionTitle).toHaveBeenCalled(); + }); + + it("#initialize should call when question page for question mtf without interactionTypes", () => { + let questionMetadata = mockData.mtfQuestionMetaData.result.question; + questionMetadata = _.omit(questionMetadata, ['interactionTypes', 'primaryCategory']) + component.questionSetId = "do_1278"; + spyOn(editorService, "fetchCollectionHierarchy").and.callFake(() => { + return of(collectionHierarchyMock); + }); + editorService.parentIdentifier = undefined; + component.questionId = "do_11330103476396851218"; + component.leafFormConfig = leafFormConfigMock; + spyOn(questionService, "readQuestion").and.returnValue( + of({result: {question: {questionMetadata}}}) + ); + spyOn(component, 'setQuestionTitle').and.callFake(() => {}); + spyOn(component, 'populateFormData').and.callFake(() => {}); + component.leafFormConfig = leafFormConfigMock; + spyOn(component, "initialize").and.callThrough(); + component.initialize(); + expect(component.initialize).toHaveBeenCalled(); + expect(component.questionPrimaryCategory).toBeUndefined(); + expect(component.questionInteractionType).toEqual("default"); + expect(component.populateFormData).toHaveBeenCalled(); + expect(component.setQuestionTitle).toHaveBeenCalled(); + }); + it("#initialize should call when question page for question slider", () => { spyOn(component, "initialize").and.callThrough(); component.initialLeafFormConfig = leafFormConfigMock; @@ -752,6 +838,12 @@ describe("QuestionComponent", () => { const templateId = "mcq-vertical"; component.getMcqQuestionHtmlBody(question, templateId); }); + + it("call #getMtfQuestionHtmlBody() to verify questionBody", () => { + const question = 'Sample Answer
'); @@ -1142,6 +1258,54 @@ describe("QuestionComponent", () => { expect(answerWrappedHtml).toBe('Sample Answer
a
", + value: "0", + }, + { + label: "b
", + value: "1", + }, + ]; + const rightOptions = [ + { + label: "c
", + value: "0", + }, + { + label: "d
", + value: "1", + }, + ]; + const matchContainer = component.getMtfAnswerContainerHtml(leftOptions, rightOptions); + expect(matchContainer).toBe('a
", + value: "0", + }, + { + label: "b
", + value: "1", + }, + ]; + const wrapperHtml = component.getOptionWrapperHtml(leftOptions, 'left'); + expect(wrapperHtml).toBe(' '); + }) + + it('#getMtfAnswerHtml() should return answer html', () => { + spyOn(component, 'getMtfAnswerHtml').and.callThrough(); + const answerHtml = component.getMtfAnswerHtml('Sample Answer
', 'left'); + expect(answerHtml).toBe('Sample Answer
Hi how are you
"; + component.editorState.options = [ + { body: "1
" }, + { body: "2
" }, + ] component.editorState.answer = ""; component.questionInteractionType = "choice"; - const toasterService = TestBed.inject(ToasterService); - spyOn(toasterService, 'error').and.callFake(() => {}); spyOn(component, 'validateChoiceQuestionData').and.callThrough(); component.validateChoiceQuestionData(); - expect(component.showFormError).toBeTruthy(); + expect(component.showFormError).toBeFalsy(); }); - it('#validateChoiceQuestionData() should validate and set showFormError to false when allowScoring is No', () => { + it("#validateMatchQuestionData() should validate match question data when all options have valid left and right values and set showFormError to false", () => { component.sourcingSettings = sourcingSettingsMock; - component.treeNodeData = {data: {metadata: {allowScoring: 'No'}}} - component.editorState = mockData.mcqQuestionMetaData.result.question; + component.editorState.question = "Match each object with its correct type
"; + component.editorState.options = [ + { left: "1
", right: "a
" }, + { left: "2
", right: "b
"}, + ] + component.editorState.correctMatchPair = [ + { "0": "0" }, + { "1": "1"}, + ]; + component.questionInteractionType = "match"; + spyOn(component, "validateMatchQuestionData").and.callThrough(); + component.validateMatchQuestionData(); + expect(component.showFormError).toBeFalsy(); + }); + + it("#validateData() should validate and set showFormError to true when allowScoring is Yes", () => { + component.treeNodeData = { data: { metadata: { allowScoring: "Yes" } } }; + component.editorState = mockData.mtfQuestionMetaData.result.question; + component.editorState.responseDeclaration.response1.mapping = []; editorService = TestBed.inject(EditorService); editorService.editorConfig.renderTaxonomy = false; - component.editorState.question = "Hi how are you
"; - component.editorState.answer = ""; - component.questionInteractionType = "choice"; + component.editorState.question = "Match each object with its correct type
"; + component.editorState.correctMatchPair = ""; + component.questionInteractionType = "match"; const toasterService = TestBed.inject(ToasterService); - spyOn(toasterService, 'error').and.callFake(() => {}); - spyOn(component, 'validateChoiceQuestionData').and.callThrough(); - component.validateChoiceQuestionData(); + spyOn(toasterService, "error").and.callFake(() => {}); + spyOn(component, "validateData").and.callThrough(); + component.validateData(component.questionInteractionType); + expect(component.showFormError).toBeTruthy(); + }); + + it("#validateData() should validate and set showFormError to false when allowScoring is No", () => { + component.treeNodeData = { data: { metadata: { allowScoring: "No" } } }; + component.editorState = mockData.mtfQuestionMetaData.result.question; + editorService = TestBed.inject(EditorService); + editorService.editorConfig.renderTaxonomy = false; + component.editorState.question = "Match each object with its correct type
"; + component.editorState.correctMatchPair = ""; + component.questionInteractionType = "match"; + const toasterService = TestBed.inject(ToasterService); + spyOn(toasterService, "error").and.callFake(() => {}); + spyOn(component, "validateData").and.callThrough(); + component.validateData(component.questionInteractionType); expect(component.showFormError).toBeFalsy(); }); diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index 7d1ab97a..52114917 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, OnInit, Output, AfterViewInit, ViewEnca import * as _ from 'lodash-es'; import { v4 as uuidv4 } from 'uuid'; import { McqForm } from '../../interfaces/McqForm'; +import { MtfForm } from '../../interfaces/MtfForm'; import { ServerResponse } from '../../interfaces/serverResponse'; import { QuestionService } from '../../services/question/question.service'; import { PlayerService } from '../../services/player/player.service'; @@ -218,31 +219,42 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } } - if (this.questionInteractionType === 'choice') { + if (this.questionInteractionType === 'choice' || this.questionInteractionType === 'match') { const responseDeclaration = this.questionMetaData.responseDeclaration; this.scoreMapping = _.get(responseDeclaration, 'response1.mapping'); const templateId = this.questionMetaData.templateId; const numberOfOptions = this.questionMetaData?.editorState?.options?.length || 0; const maximumOptions = _.get(this.questionInput, 'config.maximumOptions'); - this.editorService.optionsLength = numberOfOptions; - const options = _.map(this.questionMetaData?.editorState?.options, option => ({ body: option.value.body })); + this.editorService.optionsLength = numberOfOptions; const question = this.questionMetaData?.editorState?.question; const interactions = this.questionMetaData?.interactions; - this.editorState = new McqForm({ - question, options, answer: _.get(responseDeclaration, 'response1.correctResponse.value') - }, { templateId, numberOfOptions,maximumOptions }); - this.editorState.solutions = this.questionMetaData?.editorState?.solutions; this.editorState.interactions = interactions; - if(this.questionMetaData?.hints) { - this.editorState.hints = this.questionMetaData.hints; + if (this.questionInteractionType === 'choice') { + const options = _.map(this.questionMetaData?.editorState?.options, option => ({ body: option.value.body })); + this.editorState = new McqForm({ + question, options, answer: _.get(responseDeclaration, 'response1.correctResponse.value') + }, { templateId, numberOfOptions, maximumOptions }); } - else { + else if (this.questionInteractionType === 'match') { + const options = _.map(this.questionMetaData?.editorState?.options?.left, (left, index) => ({ + left: left.value.body, + right:this.questionMetaData?.editorState?.options?.right?.[index].value.body + })); + this.editorState = new MtfForm({ + question, options, correctMatchPair: _.get(responseDeclaration, 'response1.correctResponse.value') + }, { templateId, numberOfOptions, maximumOptions }); + } + this.editorState.solutions = this.questionMetaData?.editorState?.solutions; + if (this.questionMetaData?.hints) { + this.editorState.hints = this.questionMetaData.hints; + } else { this.editorState.hints = {}; } if (_.has(this.questionMetaData, 'responseDeclaration')) { this.editorState.responseDeclaration = _.get(this.questionMetaData, 'responseDeclaration'); } } + if (_.has(this.questionMetaData, 'primaryCategory')) { this.editorState.primaryCategory = _.get(this.questionMetaData, 'primaryCategory'); } @@ -307,11 +319,14 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { else if (this.questionInteractionType === 'choice') { this.editorState = new McqForm({ question: '', options: [] }, { numberOfOptions: _.get(this.questionInput, 'config.numberOfOptions'), maximumOptions: _.get(this.questionInput, 'config.maximumOptions') }); } - this.showLoader = false; + else if (this.questionInteractionType === 'match') { + this.editorState = new MtfForm({ question: '', options: [] }, { numberOfOptions: _.get(this.questionInput, 'config.numberOfOptions'), maximumOptions: _.get(this.questionInput, 'config.maximumOptions') }); + } /** for observation and survey to show hint,tip,dependent question option. */ if(!_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy)){ this.subMenuConfig(); } + this.showLoader = false; } }, (err: ServerResponse) => { const errInfo = { @@ -564,6 +579,11 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { this.validateChoiceQuestionData(); } + //to handle when question type is mtf + if (this.questionInteractionType === 'match') { + this.validateMatchQuestionData(); + } + if (this.questionInteractionType === 'slider') { this.validateSliderQuestionData(); } @@ -580,20 +600,22 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } validateChoiceQuestionData() { - const data = _.get(this.treeNodeData, 'data.metadata'); - if (_.get(this.editorState, 'interactionTypes[0]') === 'choice' && - _.isEmpty(this.editorState?.responseDeclaration?.response1?.mapping) && - !_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy) && - _.get(data,'allowScoring') === 'Yes') { - this.toasterService.error(_.get(this.configService, 'labelConfig.messages.error.005')); + this.validateData('choice'); + const optionValid = _.find(this.editorState.options, option => + (option.body === undefined || option.body === '' || option.length > this.setCharacterLimit)); + if (optionValid || (_.isUndefined(this.editorState.answer) && this.sourcingSettings?.enforceCorrectAnswer)) { this.showFormError = true; - return; + return; //NOSONAR } else { this.showFormError = false; } - const optionValid = _.find(this.editorState.options, option => - (option.body === undefined || option.body === '' || option.length > this.setCharacterLimit)); - if (optionValid || (_.isUndefined(this.editorState.answer) && this.sourcingSettings?.enforceCorrectAnswer)) { + } + + validateMatchQuestionData() { + this.validateData('match'); + const rightOptionValid = _.find(this.editorState.options, option => (option.right === undefined || option.right === '' || option.right.length > this.setCharacterLimit)); + const leftOptionValid = _.find(this.editorState.options, option => (option.left === undefined || option.left === '' || option.left.length > this.setCharacterLimit)); + if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.correctMatchPair) && this.sourcingSettings?.enforceCorrectAnswer)) { this.showFormError = true; return; //NOSONAR } else { @@ -601,6 +623,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } } + validateSliderQuestionData() { const min = _.get(this.sliderDatas, 'validation.range.min'); const max = _.get(this.sliderDatas, 'validation.range.max'); @@ -612,7 +635,21 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { this.showFormError = false; } } - + + validateData(interactionType) { + const data = _.get(this.treeNodeData, 'data.metadata'); + if (_.get(this.editorState, 'interactionTypes[0]') === interactionType && + _.isEmpty(this.editorState?.responseDeclaration?.response1?.mapping) && + !_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy) && + _.get(data,'allowScoring') === 'Yes') { + this.toasterService.error(_.get(this.configService, 'labelConfig.messages.error.005')); + this.showFormError = true; + return; //NOSONAR + } else { + this.showFormError = false; + } + } + redirectToQuestionset() { this.showConfirmPopup = false; this.treeService.clearTreeCache(); @@ -768,7 +805,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } setQuestionProperties(metadata) { - if (this.questionInteractionType != 'choice') { + if (this.questionInteractionType != 'choice' && this.questionInteractionType != 'match') { if (!_.isUndefined(metadata.answer)) { const answerHtml = this.getAnswerHtml(metadata.answer); const finalAnswer = this.getAnswerWrapperHtml(answerHtml); @@ -791,7 +828,14 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { }) const finalAnswer = this.getAnswerWrapperHtml(concatenatedAnswers); metadata.answer = finalAnswer; - } else if (this.questionInteractionType != 'default' && this.questionInteractionType != 'choice') { + } else if (this.questionInteractionType === 'match') { + const { question, templateId } = this.editorState; + const { left, right } = this.editorState.interactions.response1.options; + metadata.body = this.getMtfQuestionHtmlBody(question, templateId); + metadata['answer'] = metadata['correctMatchPair']; + delete metadata['correctMatchPair']; + metadata.answer = this.getMtfAnswerContainerHtml(left, right); + } else if (this.questionInteractionType !== 'default') { metadata.responseDeclaration = this.getResponseDeclaration(this.questionInteractionType); } return metadata; @@ -828,12 +872,10 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { metadata.programId = _.get(this.editorService, 'editorConfig.context.programId'); metadata.collectionId = _.get(this.editorService, 'editorConfig.context.collectionIdentifier'); metadata.organisationId = _.get(this.editorService, 'editorConfig.context.contributionOrgId'); + metadata.isReviewModificationAllowed = !!_.get(this.questionMetaData, 'isReviewModificationAllowed'); } metadata['outcomeDeclaration'] = this.getOutcomeDeclaration(metadata); metadata = _.merge(metadata, _.pickBy(this.childFormData, _.identity)); - if (_.get(this.creationContext, 'objectType') === 'question') { - metadata.isReviewModificationAllowed = !!_.get(this.questionMetaData, 'isReviewModificationAllowed'); - } // tslint:disable-next-line:max-line-length return _.omit(metadata, ['question', 'numberOfOptions', 'options', 'allowMultiSelect', 'showEvidence', 'evidenceMimeType', 'showRemarks', 'markAsNotMandatory', 'leftAnchor', 'rightAnchor', 'step', 'numberOnly', 'characterLimit', 'dateFormat', 'autoCapture', 'remarksLimit', 'maximumOptions']); } @@ -843,6 +885,32 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { const optionHtml = answerHtml.replace('{answer}', optionLabel); return optionHtml; } + + getMtfAnswerContainerHtml(leftOptions, rightOptions) { + const matchContainerTemplate = '