diff --git a/projects/questionset-editor-library-wc/src/styles.scss b/projects/questionset-editor-library-wc/src/styles.scss index 7dea030d5..79b9b530e 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 000000000..ce10ec67b --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.html @@ -0,0 +1,81 @@ + +
+ + + + + + + + + + +
+
+
+
+
+
+ + + + +
+
+ + + + +
+
+
+ +
+
+
+
+ +
+
+
diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.scss b/projects/questionset-editor-library/src/lib/components/match/match.component.scss new file mode 100644 index 000000000..f4233a529 --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.scss @@ -0,0 +1,56 @@ +.q-sb-layout-single{ + &:before{ + content: url("/assets/images/layoutoneicon.svg"); + } + &.active, + &:hover + { + border-color: var(--primary-400); + background-color: #ffffff; + color: var(--primary-400); + &:before{ + content: url("/assets/images/layoutoneicon_blue.svg"); + } + } +} +.q-sb-layout-two{ + &:before{ + content: url("/assets/images/layouttwoicon.svg"); + } + &.active, + &:hover + { + border-color: var(--primary-400); + background-color: #ffffff; + color: var(--primary-400); + &:before{ + content: url("/assets/images/layouttwoicon_blue.svg"); + } + } +} + +.b-0{ + border: 0 !important; +} + +.bg-none{ + background-color: transparent !important; + + &:hover, + &:focus { + background-color: transparent !important; + } +} + +.w-49{ + width: 49%; + max-width: 49%; +} + +.sb-line-height-24 { + line-height: 24px; +} + +.right{ + float: right; +} \ No newline at end of file diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts new file mode 100644 index 000000000..7d1a30bf2 --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts @@ -0,0 +1,114 @@ +export const mockOptionData = { + editorOptionData: { + question: "

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 000000000..39a321684 --- /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: ComponentFixture; + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + declarations: [MatchComponent, TelemetryInteractDirective], + providers: [ConfigService, EditorTelemetryService,], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MatchComponent); + component = fixture.componentInstance; + component.editorState = mockOptionData.editorOptionData; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it("#ngOnInit() should call editorDataHandler on ngOnInit", () => { + component.editorState = mockOptionData.editorOptionData; + spyOn(component, "editorDataHandler"); + component.ngOnInit(); + expect(component.editorDataHandler).toHaveBeenCalled(); + }); + + it("should not set #templateType when creating new question", () => { + component.editorState = {}; + spyOn(component, "editorDataHandler"); + component.ngOnInit(); + expect(component.templateType).toEqual("mtf-horizontal"); + }); + + it("should set #templateType when updating an existing question", () => { + component.editorState = mockOptionData.editorOptionData; + spyOn(component, "editorDataHandler"); + component.ngOnInit(); + expect(component.templateType).toEqual("mtf-vertical"); + }); + + it("ngOnChanges should not call editorDataHandler", () => { + spyOn(component, "editorDataHandler").and.callFake(() => {}); + spyOn(component, "ngOnChanges").and.callThrough(); + component.ngOnChanges({ + maxScore: new SimpleChange(undefined, 4, true), + }); + expect(component.editorDataHandler).not.toHaveBeenCalled(); + }); + + it("ngOnChanges should call editorDataHandler", () => { + spyOn(component, "editorDataHandler").and.callFake(() => {}); + spyOn(component, "ngOnChanges").and.callThrough(); + component.ngOnChanges({ + maxScore: new SimpleChange(1, 2, false), + }); + expect(component.editorDataHandler).toHaveBeenCalled(); + }); + + it('#editorDataHandler() should emit option data', () => { + spyOn(component, 'prepareMtfBody').and.callThrough(); + spyOn(component.editorDataOutput, 'emit').and.callThrough(); + component.editorState = mockOptionData.editorOptionData; + component.editorState.correctMatchPair = [{ "0": 0 }, { "1": 1 }]; + component.editorDataHandler(); + expect(component.prepareMtfBody).toHaveBeenCalledWith(mockOptionData.editorOptionData); + expect(component.editorDataOutput.emit).toHaveBeenCalled(); + }); + + it("#prepareMtfBody() should return expected mtf option data for MTF", () => { + component.maxScore = 4; + spyOn(component, 'setMapping').and.callThrough(); + spyOn(component, "getResponseDeclaration").and.callThrough(); + spyOn(component, "getInteractions").and.callThrough(); + component.prepareMtfBody(mockOptionData.editorOptionData); + expect(component.getResponseDeclaration).toHaveBeenCalledWith( + mockOptionData.editorOptionData + ); + expect(component.getInteractions).toHaveBeenCalledWith( + mockOptionData.editorOptionData.options + ); + }); + + it('#getInteractions should return expected interactions', () => { + spyOn(component, 'getInteractions').and.callThrough(); + const result = component.getInteractions(mockOptionData.editorOptionData.options); + expect(result).toEqual(mockOptionData.prepareMtfBody.interactions); + }) + + it('#setMapping should set mapping', () => { + spyOn(component, 'setMapping').and.callThrough(); + component.editorState = mockOptionData.editorOptionData; + component.editorState.correctMatchPair = mockOptionData.editorOptionData.correctMatchPair; + component.maxScore = 4; + component.setMapping(); + expect(component.mapping).toEqual(mockOptionData.prepareMtfBody.responseDeclaration.response1.mapping); + }) + + it('#setMapping should set mapping with empty array when correctMatchPair is empty', () => { + spyOn(component, 'setMapping').and.callThrough(); + component.editorState.correctMatchPair = []; + component.setMapping(); + expect(component.mapping).toEqual([]); + }); + + it('#getOutcomeDeclaration should return expected outcomeDeclaration', () => { + component.maxScore = 4; + spyOn(component, 'getOutcomeDeclaration').and.callThrough(); + const outcomeDeclaration = component.getOutcomeDeclaration(); + expect(outcomeDeclaration.maxScore.cardinality).toEqual('multiple'); + expect(outcomeDeclaration.maxScore.defaultValue).toEqual(4); + }) + + it('#getResponseDeclaration should return expected responseDeclaration', () => { + component.mapping = [ + { + value: { + "0": 0, + }, + score: 2, + }, + { + value: { + "1": 1, + }, + score: 2, + }, + ]; + spyOn(component, "getResponseDeclaration").and.callThrough(); + const responseDeclaration = component.getResponseDeclaration(mockOptionData.editorOptionData); + expect(responseDeclaration.response1.cardinality).toEqual('multiple'); + }) + + it('#setTemplate() should set #templateType to "mtf-vertical"', () => { + spyOn(component, "editorDataHandler").and.callThrough(); + const templateType = "mtf-vertical"; + component.editorState = mockOptionData.editorOptionData; + component.setTemplate(templateType); + expect(component.templateType).toEqual(templateType); + expect(component.editorDataHandler).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.ts new file mode 100644 index 000000000..0695010b4 --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.ts @@ -0,0 +1,163 @@ +import { Component, Input, OnInit, OnChanges, Output, EventEmitter, SimpleChanges } from '@angular/core'; +import * as _ from 'lodash-es'; +import { EditorTelemetryService } from '../../services/telemetry/telemetry.service'; +import { ConfigService } from '../../services/config/config.service'; + +@Component({ + selector: 'lib-match', + templateUrl: './match.component.html', + styleUrls: ['./match.component.scss'] +}) +export class MatchComponent implements OnInit, OnChanges { + @Input() editorState: any; + @Input() showFormError; + @Input() questionPrimaryCategory; + @Input() mapping = []; + @Input() isReadOnlyMode; + @Input() maxScore; + @Output() editorDataOutput: EventEmitter = new EventEmitter(); + public setCharacterLimit = 160; + public templateType = 'mtf-horizontal'; + + constructor( + public telemetryService: EditorTelemetryService, + public configService: ConfigService, + ) {} + + ngOnInit() { + if (!_.isUndefined(this.editorState.templateId)) { + this.templateType = this.editorState.templateId; + } + this.mapping = _.get(this.editorState, 'responseDeclaration.response1.mapping') || []; + this.editorDataHandler(); + } + ngOnChanges(changes: SimpleChanges){ + if (!_.isUndefined(changes.maxScore.previousValue) && !_.isNaN(changes.maxScore.currentValue)) { + this.setMapping(); + this.editorDataHandler(); + } + } + + editorDataHandler(event?) { + const body = this.prepareMtfBody(this.editorState); + this.editorDataOutput.emit({ + body, + mediaobj: event ? event.mediaobj : undefined, + }); + } + prepareMtfBody(editorState) { + let metadata: any; + if (!_.isEmpty(editorState.options)) { + editorState.correctMatchPair = editorState.options.map((option, index) => { + const correctMatchPair = {}; + correctMatchPair[index.toString()] = index; + return correctMatchPair; + }); + } + this.setMapping(); + let options: any; + if (!_.isEmpty(editorState.correctMatchPair)) { + options = { + left: editorState.options.map((option, index) => { + return { + value: { + body: option.left, + value: index, + } + } + }), + right: editorState.options.map((option, index) => { + return { + value: { + body: option.right, + value: index, + } + } + }) + } + } + metadata = { + templateId: this.templateType, + name: this.questionPrimaryCategory || "Match The Following Question", + responseDeclaration: this.getResponseDeclaration(editorState), + outcomeDeclaration: this.getOutcomeDeclaration(), + interactionTypes: ["match"], + interactions: this.getInteractions(editorState.options), + editorState: { + options, + }, + qType: "MTF", + primaryCategory: + this.questionPrimaryCategory || "Match The Following Question", + }; + return metadata; + } + getResponseDeclaration(editorState) { + const responseDeclaration = { + response1: { + cardinality: 'multiple', + type: 'map', + correctResponse: { + value: editorState.correctMatchPair, + }, + mapping: this.mapping, + }, + }; + return responseDeclaration; + } + + getOutcomeDeclaration() { + const outcomeDeclaration = { + maxScore: { + cardinality: 'multiple', + type: 'integer', + defaultValue: this.maxScore + } + }; + return outcomeDeclaration; + } + + setMapping() { + if (!_.isEmpty(this.editorState.correctMatchPair)) { + this.mapping = []; + const scoreForEachMatch = _.round( + this.maxScore / this.editorState.correctMatchPair.length, + 2 + ); + _.forEach(this.editorState.correctMatchPair, (value) => { + const optionMapping = { + value: value, + score: scoreForEachMatch, + }; + this.mapping.push(optionMapping); + }) + } else { + this.mapping = []; + } + } + + getInteractions(options) { + const optionSet = { + left: options.map((option,index) => ({ + label: option.left, + value: index, + })), + right: options.map((option,index) => ({ + label: option.right, + value: index, + })), + } + const interactions = { + response1: { + type: 'match', + options: optionSet, + } + }; + return interactions; + } + + setTemplate(template) { + this.templateType = template; + this.editorDataHandler(); + } +} diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.html b/projects/questionset-editor-library/src/lib/components/question/question.component.html index a5c48b1b5..1d9d78527 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.html +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.html @@ -64,6 +64,13 @@ (editorDataOutput)="editorDataHandler($event)" [sourcingSettings]="sourcingSettings" [mapping]="scoreMapping" [maxScore]="maxScore" [isReadOnlyMode]="isReadOnlyMode"> + + diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.data.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.data.ts index cb904dbc4..f5529c520 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.data.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.data.ts @@ -239,6 +239,211 @@ export const mockData = { }, }, }, + mtfQuestionMetaData: { + id: "api.question.read", + ver: "3.0", + ts: "2022-01-31T04:38:30ZZ", + params: { + resmsgid: "597b1b63-7007-435a-8b9d-68127f3c6fa8", + msgid: null, + err: null, + status: "successful", + errmsg: null, + }, + responseCode: "OK", + result: { + question: { + mimeType: "application/vnd.sunbird.question", + media: [], + editorState: { + options: { + left: [ + { + value: { + body: "

LeftOption1

", + 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

", + creator: "Arpan Gupta", + createdBy: "5a587cc1-e018-4859-a0a8-e842650b9d64", + board: "CBSE", + medium: ["English"], + gradeLevel: ["Grade 1"], + subject: ["English"], + topic: ["Forest"], + author: "check1@yopmail.com", + channel: "01309282781705830427", + framework: "nit_k-12", + license: "CC BY 4.0", + maxScore: "4", + identifier: "", + }, + }, + }, sliderQuestionMetaData: { id: "api.question.read", ver: "3.0", @@ -3170,6 +3375,113 @@ export const interactionChoiceEditorState = { primaryCategory: 'Multiple Choice Question' }; +export const interactionMatchEditorState = { + 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 e8bcf3e36..a102e1fc2 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 = '
{question}
'; + const templateId = "mtf-horizontal"; + component.getMtfQuestionHtmlBody(question, templateId); + }); it("Unit test for #sendForReview", () => { spyOn(component, "upsertQuestion"); @@ -1130,6 +1222,30 @@ describe("QuestionComponent", () => { expect(metadata['outcomeDeclaration'].maxScore.defaultValue).toEqual(1); }); + it('#getQuestionMetadata() should return question metadata when interactionType is match', () => { + component.mediaArr = []; + component.editorState = interactionMatchEditorState; + component.selectedSolutionType = 'video'; + component.creationContext = undefined; + component.questionInteractionType = 'match'; + component.childFormData = { + name: 'MTF', + bloomsLevel: null, + board: 'CBSE', + maxScore: 1 + }; + component.maxScore = 4; + spyOn(component, 'getDefaultSessionContext').and.returnValue({ + creator: 'Vaibahv Bhuva', + createdBy: '5a587cc1-e018-4859-a0a8-e842650b9d64' + } + ); + spyOn(component, 'getQuestionSolution').and.returnValue({}); + spyOn(component, 'getQuestionMetadata').and.callThrough(); + const metadata = component.getQuestionMetadata(); + expect(metadata['outcomeDeclaration'].maxScore.defaultValue).toEqual(4); + }); + it('#getAnswerHtml() should return answer html', () => { spyOn(component, 'getAnswerHtml').and.callThrough(); const answerHtml = component.getAnswerHtml('

Sample Answer

'); @@ -1142,6 +1258,54 @@ describe("QuestionComponent", () => { expect(answerWrappedHtml).toBe('

Sample Answer

'); }); + it('#getMtfAnswerContainerHtml() should return answer html', () => { + spyOn(component, 'getMtfAnswerContainerHtml').and.callThrough(); + const leftOptions = [ + { + label: "

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

b

c

d

') + }) + + it('#getOptionWrapperHtml() should return wrapper html', () => { + spyOn(component, 'getOptionWrapperHtml').and.callThrough(); + const leftOptions = [ + { + label: "

a

", + value: "0", + }, + { + label: "

b

", + value: "1", + }, + ]; + const wrapperHtml = component.getOptionWrapperHtml(leftOptions, 'left'); + expect(wrapperHtml).toBe('

a

b

'); + }) + + it('#getMtfAnswerHtml() should return answer html', () => { + spyOn(component, 'getMtfAnswerHtml').and.callThrough(); + const answerHtml = component.getMtfAnswerHtml('

Sample Answer

', 'left'); + expect(answerHtml).toBe('

Sample Answer

'); + }) + it('#getInteractionValues() should return correct answer object', () => { spyOn(component, 'getInteractionValues').and.callThrough(); const correctAnswersData = component.getInteractionValues([0], interactionChoiceEditorState.interactions); @@ -1348,36 +1512,65 @@ describe("QuestionComponent", () => { expect(component.validateChoiceQuestionData).toHaveBeenCalled(); }); - it('#validateChoiceQuestionData() should validate and set showFormError to true', () => { + it('#validateChoiceQuestionData() should validate choice question data when all options are valid and set showFormError to false', () => { component.sourcingSettings = sourcingSettingsMock; - component.treeNodeData = {data: {metadata: {allowScoring: 'Yes'}}} - component.editorState = mockData.mcqQuestionMetaData.result.question; - component.editorState.responseDeclaration.response1.mapping = []; - editorService = TestBed.inject(EditorService); - editorService.editorConfig.renderTaxonomy = false; component.editorState.question = "

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 7d1ab97a9..52114917b 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 = '
{leftOptions}{rightOptions}
'; + const leftOptionsHtml = this.getOptionWrapperHtml(leftOptions, 'left'); + const rightOptionsHtml = this.getOptionWrapperHtml(rightOptions, 'right'); + const matchContainer = matchContainerTemplate + .replace('{leftOptions}', leftOptionsHtml) + .replace('{rightOptions}', rightOptionsHtml); + return matchContainer; + } + + getOptionWrapperHtml(options, type) { + const wrapperTemplate = `
{options}
`; + let optionsHtml = ''; + options.forEach((option) => { + const optionHtml = this.getMtfAnswerHtml(option.label, type); + optionsHtml = optionsHtml.concat(optionHtml); + }); + const wrapper = wrapperTemplate.replace('{options}', optionsHtml); + return wrapper; + } + getMtfAnswerHtml(label, type) { + const answerHtml = `
{label}
`; + const optionHtml = answerHtml.replace('{label}', label); + return optionHtml; + } getAnswerWrapperHtml(concatenatedAnswers) { const answerTemplate = '
{answers}
'; @@ -887,6 +955,15 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { return videoSolutionValue; } + getMtfQuestionHtmlBody(question, templateId) { + const matchTemplateConfig = { + // tslint:disable-next-line:max-line-length + matchBody: '
{question}
' + }; + const { matchBody } = matchTemplateConfig; + const questionBody = matchBody.replace('{templateClass}', templateId).replace('{question}', question); + return questionBody; + } getMcqQuestionHtmlBody(question, templateId) { const mcqTemplateConfig = { diff --git a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts new file mode 100644 index 000000000..67713877e --- /dev/null +++ b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts @@ -0,0 +1,55 @@ +import * as _ from "lodash-es"; + +export class MtfOption { + constructor(public left: string, public right: string) {} +} + +export interface MtfData { + question: string; + options: Array; + correctMatchPair?: string; + learningOutcome?: string; + complexityLevel?: string; + maxScore?: number; +} + +export interface MtfConfig { + templateId?: string; + numberOfOptions?: number; + maximumOptions?: number; +} + +export class MtfForm { + public question: string; + public options: Array; + public templateId: string; + public correctMatchPair: string; + public learningOutcome?: string; + public complexityLevel?: string; + public maxScore?: number; + public maximumOptions; + public numberOfOptions; + + constructor({question, options, correctMatchPair, learningOutcome, complexityLevel, maxScore,}: MtfData,{ templateId, numberOfOptions, maximumOptions }: MtfConfig) { + this.question = question; + this.options = options || []; + this.templateId = templateId; + this.correctMatchPair = correctMatchPair; + this.learningOutcome = learningOutcome; + this.complexityLevel = complexityLevel; + this.numberOfOptions = numberOfOptions || 2; + this.maximumOptions = maximumOptions || 4; + this.maxScore = maxScore; + if (!this.options?.length) { + _.times(this.numberOfOptions, index => this.options.push(new MtfOption('', ''))); + } + } + + addOptions() { + this.options.push(new MtfOption('', '')); + } + + deleteOptions(position: number) { + this.options.splice(position, 1); + } +} diff --git a/projects/questionset-editor-library/src/lib/questionset-editor-library.module.ts b/projects/questionset-editor-library/src/lib/questionset-editor-library.module.ts index a05ca9d31..b5e69138e 100644 --- a/projects/questionset-editor-library/src/lib/questionset-editor-library.module.ts +++ b/projects/questionset-editor-library/src/lib/questionset-editor-library.module.ts @@ -35,6 +35,7 @@ import { PlainTreeComponent } from './components/plain-tree/plain-tree.component import { A11yModule } from '@angular/cdk/a11y'; import { ProgressStatusComponent } from './components/progress-status/progress-status.component'; import {TermAndConditionComponent} from './components/term-and-condition/term-and-condition.component'; +import { MatchComponent } from './components/match/match.component'; import { QualityParamsModalComponent } from './components/quality-params-modal/quality-params-modal.component'; @NgModule({ @@ -66,7 +67,8 @@ import { QualityParamsModalComponent } from './components/quality-params-modal/q PlainTreeComponent, ProgressStatusComponent, TermAndConditionComponent, - QualityParamsModalComponent + QualityParamsModalComponent, + MatchComponent ], imports: [CommonModule, FormsModule, ReactiveFormsModule.withConfig({callSetDisabledState: 'whenDisabledForLegacyCode'}), RouterModule.forChild([]), SuiModule, CommonFormElementsModule, InfiniteScrollModule, HttpClientModule, ResourceLibraryModule, A11yModule], diff --git a/projects/questionset-editor-library/src/lib/services/config/editor.config.json b/projects/questionset-editor-library/src/lib/services/config/editor.config.json index 2c5a544a5..ca85ae50f 100644 --- a/projects/questionset-editor-library/src/lib/services/config/editor.config.json +++ b/projects/questionset-editor-library/src/lib/services/config/editor.config.json @@ -16,7 +16,7 @@ "accepted": "mp4, webm" } }, - "questionPrimaryCategories": ["Multiple Choice Question", "Subjective Question"], + "questionPrimaryCategories": ["Multiple Choice Question", "Subjective Question", "Match The Following Question"], "contentPrimaryCategories": ["Course Assessment", "eTextbook", "Explanation Content", "Learning Resource", "Practice Question Set"], "readQuestionFields": "body,primaryCategory,mimeType,qType,answer,templateId,responseDeclaration,interactionTypes,interactions,name,solutions,editorState,media,remarks,evidence,hints,instructions,outcomeDeclaration,", "omitFalseyProperties":["topic", "topicsIds", "targetTopicIds", "keywords"], diff --git a/projects/questionset-editor-library/src/lib/services/config/label.config.json b/projects/questionset-editor-library/src/lib/services/config/label.config.json index 27b3f8dc1..7b1ea5962 100644 --- a/projects/questionset-editor-library/src/lib/services/config/label.config.json +++ b/projects/questionset-editor-library/src/lib/services/config/label.config.json @@ -44,7 +44,8 @@ "remove_btn_label":"Remove", "done_btn_label":"Done", "add_translation_label":"Add Translation", - "add_page_numbers_to_questions_btn_label": "Pagination" + "add_page_numbers_to_questions_btn_label": "Pagination", + "delete_pair_btn_label":"Delete Pair" }, "lbl":{ "Questiondetails":"Question details", @@ -130,7 +131,10 @@ "editingConsentNote": "I confirm that I am allowing my reviewer to make edits to the content and metadata of the content. I consent my reviewer make changes, if any and publish the contain thereafter.", "acceptBothConsentNote": "Agree to both conditions", "totalScore": "Total Score", - "qualityReview": "If the score is less than or equal to 15 please re-evaluate the question and send back for corrections." + "qualityReview": "If the score is less than or equal to 15 please re-evaluate the question and send back for corrections.", + "addPair": "Add pair", + "setAnswers": "Set your answers", + "addQuestionAnswerPairText": "Add question-answer pairs to your question. Answers will be shuffled automatically" }, "err":{ "somethingWentWrong":"Something went wrong", diff --git a/sonar-project.properties b/sonar-project.properties index 30752dd1d..10f91e1bf 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ sonar.projectName=sunbird-questionset-editor sonar.language=ts sonar.sources=projects/questionset-editor-library/src -sonar.exclusions=projects/questionset-editor-library/src/lib/assets/**,projects/questionset-editor-library/src/lib/**/*.spec.ts,projects/questionset-editor-library/src/lib/**/*.spec.data.ts,projects/questionset-editor-library/src/lib/**/*.module.ts,projects/questionset-editor-library/src/lib/services/config/label.config.json +sonar.exclusions=projects/questionset-editor-library/src/lib/assets/**,projects/questionset-editor-library/src/lib/**/*.spec.ts,projects/questionset-editor-library/src/lib/**/*.spec.data.ts,projects/questionset-editor-library/src/lib/**/*.module.ts,projects/questionset-editor-library/src/lib/interfaces/*,projects/questionset-editor-library/src/lib/services/config/label.config.json sonar.javascript.lcov.reportPaths=projects/questionset-editor-library/coverage/lcov.info sonar.projectKey=Sunbird-inQuiry_editor sonar.host.url=https://sonarcloud.io diff --git a/src/app/data.ts b/src/app/data.ts index a6dbfed69..1f238f1a3 100644 --- a/src/app/data.ts +++ b/src/app/data.ts @@ -138,7 +138,8 @@ export const questionSetEditorConfig = { children: { Question: [ 'Multiple Choice Question', - 'Subjective Question' + 'Subjective Question', + 'Match The Following Question', ] }, addFromLibrary: false,