diff --git a/packages/app/obojobo-document-engine/src/scripts/oboeditor/documents/oboeditor-tutorial.json b/packages/app/obojobo-document-engine/src/scripts/oboeditor/documents/oboeditor-tutorial.json index 3aa5bbfbda..b8a85d10f7 100644 --- a/packages/app/obojobo-document-engine/src/scripts/oboeditor/documents/oboeditor-tutorial.json +++ b/packages/app/obojobo-document-engine/src/scripts/oboeditor/documents/oboeditor-tutorial.json @@ -7209,13 +7209,13 @@ { "data": {}, "text": { - "value": "Pick all correct answers questions allow a student to select as many answers as they want. A student must select all the correct choices to get the points for that question.", + "value": "Pick all correct answers questions allow a student to select as many answers as they want. By default, a student must select all the correct choices to get the points for that question.", "styleList": [ { - "end": 105, + "end": 117, "data": {}, "type": "b", - "start": 100 + "start": 112 } ] } @@ -7224,6 +7224,29 @@ }, "children": [] }, + { + "id": "e081e958-6130-40b6-b4c4-30b956a61143", + "type": "ObojoboDraft.Chunks.Text", + "content": { + "textGroup": [ + { + "data": {}, + "text": { + "value": "Alternatively, Partial Scoring can be turned on which allows students to receive partial credit for the question based on how many correct and incorrect choices they select.", + "styleList": null + } + }, + { + "data": {}, + "text": { + "value": "In the question below, selecting only one correct answer would result in a 50%, and selecting one correct answer along with the incorrect answer would result in a 0%. Selecting all three answer choices would result in a 50% as well, as the incorrect choice cancels out one of the correct choices.", + "styleList": null + } + } + ] + }, + "children": [] + }, { "id": "a687a99d-c517-4dc9-9bdd-4c3352d3618f", "type": "ObojoboDraft.Chunks.Question", @@ -7249,6 +7272,15 @@ } ] } + }, + { + "data": { + "indent": 0 + }, + "text": { + "value": "If Partial Scoring is enabled, then the student can receive partial credit even if they didn't get it completely correct.", + "styleList": null + } } ] }, diff --git a/packages/app/obojobo-repository/shared/components/stats/assessment-stats-controls.scss b/packages/app/obojobo-repository/shared/components/stats/assessment-stats-controls.scss index 07858890f7..11300063e0 100644 --- a/packages/app/obojobo-repository/shared/components/stats/assessment-stats-controls.scss +++ b/packages/app/obojobo-repository/shared/components/stats/assessment-stats-controls.scss @@ -5,6 +5,31 @@ margin-bottom: 1em; } + .filter-controls { + display: flex; + flex-direction: column; + width: 100%; + + hr { + margin-top: 1em; + } + + input { + margin-right: 0.5em; + margin-left: 0; + } + + label { + cursor: pointer; + display: flex; + align-items: center; + + span { + white-space: nowrap; + } + } + } + .search-controls { select { @include select-input(); @@ -90,29 +115,4 @@ font-family: $font-monospace; } } - - .filter-controls { - display: flex; - flex-direction: column; - width: 100%; - - hr { - margin-top: 1em; - } - - input { - margin-right: 0.5em; - margin-left: 0; - } - - label { - cursor: pointer; - display: flex; - align-items: center; - - span { - white-space: nowrap; - } - } - } } diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/__snapshots__/adapter.test.js.snap b/packages/obonode/obojobo-chunks-multiple-choice-assessment/__snapshots__/adapter.test.js.snap index 90146ee0ec..a5430cc36c 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/__snapshots__/adapter.test.js.snap +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/__snapshots__/adapter.test.js.snap @@ -3,6 +3,7 @@ exports[`MCAssessment adapter can convert to JSON 1`] = ` Object { "content": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": true, }, @@ -12,6 +13,7 @@ Object { exports[`MCAssessment adapter can convert to JSON WITH attributes 1`] = ` Object { "content": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": true, }, @@ -21,6 +23,7 @@ Object { exports[`MCAssessment adapter construct builds with attributes 1`] = ` Object { "modelState": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": false, }, @@ -30,6 +33,7 @@ Object { exports[`MCAssessment adapter construct builds with responseType 1`] = ` Object { "modelState": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": true, }, @@ -39,6 +43,7 @@ Object { exports[`MCAssessment adapter construct builds with shuffle 1`] = ` Object { "modelState": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": true, }, @@ -48,6 +53,7 @@ Object { exports[`MCAssessment adapter construct builds without attributes 1`] = ` Object { "modelState": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": true, }, diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/adapter.js b/packages/obonode/obojobo-chunks-multiple-choice-assessment/adapter.js index 05cc36fafb..5a0bfc46a6 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/adapter.js +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/adapter.js @@ -4,16 +4,19 @@ const Adapter = { model.modelState.responseType = content.responseType || 'pick-one' model.modelState.shuffle = content.shuffle !== false + model.modelState.partialScoring = content.partialScoring === true }, clone(model, clone) { clone.modelState.responseType = model.modelState.responseType clone.modelState.shuffle = model.modelState.shuffle + clone.modelState.partialScoring = model.modelState.partialScoring }, toJSON(model, json) { json.content.responseType = model.modelState.responseType json.content.shuffle = model.modelState.shuffle + json.content.partialScoring = model.modelState.partialScoring } } diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/adapter.test.js b/packages/obonode/obojobo-chunks-multiple-choice-assessment/adapter.test.js index f73058af9a..3549d6dc8e 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/adapter.test.js +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/adapter.test.js @@ -12,7 +12,8 @@ describe('MCAssessment adapter', () => { const attrs = { content: { responseType: 'pick-one', - shuffle: false + shuffle: false, + partialScoring: false } } @@ -62,19 +63,22 @@ describe('MCAssessment adapter', () => { const a = { modelState: { responseType: 'pick-one', - shuffle: true + shuffle: true, + partialScoring: false } } const attrs = { content: { responseType: 'pick-one', - shuffle: true + shuffle: true, + partialScoring: false } } const b = { modelState: { responseType: null, - shuffle: false + shuffle: false, + partialScoring: false } } @@ -101,7 +105,8 @@ describe('MCAssessment adapter', () => { const attrs = { content: { responseType: 'pick-one', - shuffle: true + shuffle: true, + partialScoring: false } } const json = { content: {} } diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/converter.js b/packages/obonode/obojobo-chunks-multiple-choice-assessment/converter.js index b379712ceb..569c659fb5 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/converter.js +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/converter.js @@ -38,7 +38,8 @@ const slateToObo = node => { triggers: node.content.triggers, objectives: node.content.objectives, responseType, - shuffle: node.content.shuffle + shuffle: node.content.shuffle, + partialScoring: node.content.partialScoring }) } } diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/empty-node.json b/packages/obonode/obojobo-chunks-multiple-choice-assessment/empty-node.json index 292182baeb..fb43eec519 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/empty-node.json +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/empty-node.json @@ -2,7 +2,8 @@ "type": "ObojoboDraft.Chunks.MCAssessment", "content": { "responseType": "pick-one", - "shuffle": true + "shuffle": true, + "partialScoring": false }, "children": [ { diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/server/mcassessment.js b/packages/obonode/obojobo-chunks-multiple-choice-assessment/server/mcassessment.js index 5ff85e4ae2..1891a8b5b5 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/server/mcassessment.js +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/server/mcassessment.js @@ -22,14 +22,22 @@ class MCAssessment extends DraftNode { }) ) + const partialScoring = this.node.content.partialScoring || false const responseIds = new Set(responseRecord.response.ids) - if (correctIds.size !== responseIds.size) return setScore(0) + let score, + numCorrect = 0 - let score = 100 - correctIds.forEach(id => { - if (!responseIds.has(id)) score = 0 + responseIds.forEach(id => { + if (correctIds.has(id)) numCorrect++ + else numCorrect-- }) + + if (!partialScoring && numCorrect !== correctIds.size) return setScore(0) + + if (numCorrect <= 0) score = 0 + else score = (100 * numCorrect) / correctIds.size + setScore(score) break } diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/server/mcassessment.test.js b/packages/obonode/obojobo-chunks-multiple-choice-assessment/server/mcassessment.test.js index b1261b7847..0aeb9fff57 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/server/mcassessment.test.js +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/server/mcassessment.test.js @@ -66,10 +66,20 @@ describe('MCAssessment', () => { expect(setScore).toHaveBeenCalledWith(0) }) - test('onCalculateScore sets score to 0 if number of chosen !== number of correct answers (pick-all)', () => { + test('onCalculateScore determines partial score if number of chosen !== number of correct answers (pick-all)', () => { const question = { contains: () => true } const responseRecord = { response: { ids: ['test'] } } - mcAssessment.node.content = { responseType: 'pick-all' } + mcAssessment.node.content = { responseType: 'pick-all', partialScoring: true } + + expect(setScore).not.toHaveBeenCalled() + mcAssessment.onCalculateScore({}, question, responseRecord, setScore) + expect(setScore).toHaveBeenCalledWith(50) + }) + + test('onCalculateScore does not give negative score with partial scoring (pick-all)', () => { + const question = { contains: () => true } + const responseRecord = { response: { ids: ['test2', 'test3'] } } + mcAssessment.node.content = { responseType: 'pick-all', partialScoring: true } expect(setScore).not.toHaveBeenCalled() mcAssessment.onCalculateScore({}, question, responseRecord, setScore) diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/viewer-component.js b/packages/obonode/obojobo-chunks-multiple-choice-assessment/viewer-component.js index 8e18d7da8b..a6ce61ceeb 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/viewer-component.js +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/viewer-component.js @@ -119,17 +119,23 @@ export default class MCAssessment extends OboQuestionAssessmentComponent { switch (this.props.model.modelState.responseType) { case 'pick-all': { - if (correct.size !== responses.size) { - return { score: 0, details: null } - } - - let score = 100 - correct.forEach(function(id) { - if (!responses.has(id)) { - score = 0 + let score, + numCorrect = 0 + + responses.forEach(function(id) { + if (correct.has(id)) { + numCorrect++ + } else { + numCorrect-- } }) + if (numCorrect <= 0) { + score = 0 + } else { + score = (100 * numCorrect) / correct.size + } + return { score, details: null } } diff --git a/packages/obonode/obojobo-chunks-multiple-choice-assessment/viewer-component.test.js b/packages/obonode/obojobo-chunks-multiple-choice-assessment/viewer-component.test.js index 0f4e1da696..74a9276465 100644 --- a/packages/obonode/obojobo-chunks-multiple-choice-assessment/viewer-component.test.js +++ b/packages/obonode/obojobo-chunks-multiple-choice-assessment/viewer-component.test.js @@ -238,8 +238,8 @@ describe('MCAssessmentViewerComponent', () => { ${'pick-one-multiple-correct'} | ${['b', 'c', 'd']} | ${100} ${'pick-one-multiple-correct'} | ${['a', 'b', 'c', 'd']} | ${100} ${'pick-all'} | ${[]} | ${0} - ${'pick-all'} | ${['a']} | ${0} - ${'pick-all'} | ${['b']} | ${0} + ${'pick-all'} | ${['a']} | ${50} + ${'pick-all'} | ${['b']} | ${50} ${'pick-all'} | ${['c']} | ${0} ${'pick-all'} | ${['d']} | ${0} ${'pick-all'} | ${['a', 'b']} | ${100} @@ -248,8 +248,8 @@ describe('MCAssessmentViewerComponent', () => { ${'pick-all'} | ${['b', 'c']} | ${0} ${'pick-all'} | ${['b', 'd']} | ${0} ${'pick-all'} | ${['c', 'd']} | ${0} - ${'pick-all'} | ${['a', 'b', 'c']} | ${0} - ${'pick-all'} | ${['a', 'b', 'd']} | ${0} + ${'pick-all'} | ${['a', 'b', 'c']} | ${50} + ${'pick-all'} | ${['a', 'b', 'd']} | ${50} ${'pick-all'} | ${['a', 'c', 'd']} | ${0} ${'pick-all'} | ${['b', 'c', 'd']} | ${0} ${'pick-all'} | ${['a', 'b', 'c', 'd']} | ${0} diff --git a/packages/obonode/obojobo-chunks-question/editor-component.js b/packages/obonode/obojobo-chunks-question/editor-component.js index 8f45b50905..03e4feebc9 100644 --- a/packages/obonode/obojobo-chunks-question/editor-component.js +++ b/packages/obonode/obojobo-chunks-question/editor-component.js @@ -8,7 +8,7 @@ import withSlateWrapper from 'obojobo-document-engine/src/scripts/oboeditor/comp import React from 'react' import Node from 'obojobo-document-engine/src/scripts/oboeditor/components/node/editor-component' -const { Button } = Common.components +const { Button, MoreInfoButton } = Common.components const QUESTION_NODE = 'ObojoboDraft.Chunks.Question' const SOLUTION_NODE = 'ObojoboDraft.Chunks.Question.Solution' const MCASSESSMENT_NODE = 'ObojoboDraft.Chunks.MCAssessment' @@ -30,7 +30,7 @@ const Question = props => { }) } - const toggleCollapsed = () => { + function toggleCollapsed() { const path = ReactEditor.findPath(props.editor, props.element) const collapsed = !props.element.content.collapsed @@ -60,6 +60,24 @@ const Question = props => { ) } + function onSetScoring(event) { + const hasSolution = getHasSolution() + let assessmentNode + + if (hasSolution) { + assessmentNode = props.element.children[props.element.children.length - 2] + } else { + assessmentNode = props.element.children[props.element.children.length - 1] + } + + const path = ReactEditor.findPath(props.editor, assessmentNode) + return Transforms.setNodes( + props.editor, + { content: { ...assessmentNode.content, partialScoring: event.target.checked } }, + { at: path } + ) + } + function getHasSolution() { return props.element.children[props.element.children.length - 1].subtype === SOLUTION_NODE } @@ -165,15 +183,14 @@ const Question = props => { const hasSolution = getHasSolution() const isInAssessment = getIsInAssessment() - let questionType - - // The question type is determined by the MCAssessment or the NumericAssessement - // This is either the last node or the second to last node - if (hasSolution) { - questionType = element.children[element.children.length - 2].type - } else { - questionType = element.children[element.children.length - 1].type - } + + // The question type is determined by the MCAssessment or the NumericAssessment + // This is either the last node or the second to last node depending on whether the + // 'explanation' area is visible + const questionElement = element.children[element.children.length - (hasSolution ? 2 : 1)] + const questionType = questionElement.type + + const partialScoring = questionElement.content?.partialScoring || false const className = 'component obojobo-draft--chunks--question is-viewed pad' + @@ -204,6 +221,25 @@ const Question = props => { Multiple choice Input a number + {questionType === MCASSESSMENT_NODE && + questionElement.content?.responseType === 'pick-all' ? ( + + + + + + Students will earn partial credit based on how many of the correct + answers they select. + + + + + + + Partial Scoring + + + ) : null} Survey Only diff --git a/packages/obonode/obojobo-chunks-question/editor-component.scss b/packages/obonode/obojobo-chunks-question/editor-component.scss index 8f87155dbe..734daca033 100644 --- a/packages/obonode/obojobo-chunks-question/editor-component.scss +++ b/packages/obonode/obojobo-chunks-question/editor-component.scss @@ -36,6 +36,25 @@ vertical-align: middle; } + .scoring-explanation { + position: absolute; + right: 17.25em; + z-index: $z-index-above-all; + + .text-container { + font-family: $font-default; + font-size: 0.9rem; + width: 15rem; + + > .text { + padding: 0.5rem; + margin: 0.5rem; + background: $color-bg; + border-radius: $dimension-rounded-radius; + } + } + } + .question-type { position: absolute; top: 2.85em; @@ -48,6 +67,10 @@ font-size: 0.7em; cursor: pointer; + &.scoring { + right: 13.7em; + } + &:hover { border: 1px solid $color-action; background: lighten($color-action, 48%); diff --git a/packages/obonode/obojobo-chunks-question/editor-component.test.js b/packages/obonode/obojobo-chunks-question/editor-component.test.js index cb4427bd6c..b318c16830 100644 --- a/packages/obonode/obojobo-chunks-question/editor-component.test.js +++ b/packages/obonode/obojobo-chunks-question/editor-component.test.js @@ -33,7 +33,9 @@ jest.mock('obojobo-document-engine/src/scripts/common', () => ({ }, components: { // eslint-disable-next-line react/display-name - Button: props => {props.children} + Button: props => {props.children}, + // eslint-disable-next-line react/display-name + MoreInfoButton: props => {props.children} }, util: { ModalUtil: { @@ -64,7 +66,7 @@ describe('Question Editor Node', () => { const props = { element: { content: { type: 'default' }, - children: [{}, { subtype: SOLUTION_NODE }] + children: [{ content: {} }, { subtype: SOLUTION_NODE }] } } const component = renderer.create() @@ -106,7 +108,7 @@ describe('Question Editor Node', () => { const props = { element: { content: { type: 'default' }, - children: [{}, { subtype: SOLUTION_NODE }] + children: [{ content: {} }, { subtype: SOLUTION_NODE }] } } @@ -124,7 +126,7 @@ describe('Question Editor Node', () => { const props = { element: { content: { type: 'survey' }, - children: [{}] + children: [{ content: {} }] } } const component = renderer.create() @@ -137,7 +139,7 @@ describe('Question Editor Node', () => { const props = { element: { content: { type: 'default' }, - children: [{}] + children: [{ content: {} }] } } const component = mount() @@ -155,7 +157,7 @@ describe('Question Editor Node', () => { const props = { element: { content: { type: 'default' }, - children: [{}] + children: [{ content: {} }] } } const component = mount() @@ -177,7 +179,10 @@ describe('Question Editor Node', () => { editor: {}, element: { content: { type: 'default' }, - children: [{ type: BREAK_NODE }, { id: 'mock-mca-id', type: MCASSESSMENT_NODE }] + children: [ + { type: BREAK_NODE }, + { id: 'mock-mca-id', type: MCASSESSMENT_NODE, content: {} } + ] } } const component = mount() @@ -185,8 +190,15 @@ describe('Question Editor Node', () => { const pathOfMCAssessment = [9, 2, 1] ReactEditor.findPath.mockReturnValue(path) + // by default the 'partial scoring' checkbox should not be rendered + // make sure there's only one - survey mode + expect(component.find({ type: 'checkbox' }).length).toBe(1) + // turn ON survey - component.find({ type: 'checkbox' }).simulate('change', { target: { checked: true } }) + component + .find({ type: 'checkbox' }) + .at(0) + .simulate('change', { target: { checked: true } }) component.update() @@ -204,7 +216,10 @@ describe('Question Editor Node', () => { Transforms.setNodes.mockClear() // turn OFF survey - component.find({ type: 'checkbox' }).simulate('change', { target: { checked: false } }) + component + .find({ type: 'checkbox' }) + .at(0) + .simulate('change', { target: { checked: false } }) component.update() @@ -227,7 +242,7 @@ describe('Question Editor Node', () => { content: { type: 'default' }, children: [ { type: BREAK_NODE }, - { id: 'mock-mca-id', type: MCASSESSMENT_NODE }, + { id: 'mock-mca-id', type: MCASSESSMENT_NODE, content: {} }, { subtype: SOLUTION_NODE } ] } @@ -238,7 +253,10 @@ describe('Question Editor Node', () => { ReactEditor.findPath.mockReturnValue(path) // turn ON survey - component.find({ type: 'checkbox' }).simulate('change', { target: { checked: true } }) + component + .find({ type: 'checkbox' }) + .at(0) + .simulate('change', { target: { checked: true } }) component.update() @@ -256,7 +274,10 @@ describe('Question Editor Node', () => { Transforms.setNodes.mockClear() // turn OFF survey - component.find({ type: 'checkbox' }).simulate('change', { target: { checked: false } }) + component + .find({ type: 'checkbox' }) + .at(0) + .simulate('change', { target: { checked: false } }) component.update() @@ -272,6 +293,167 @@ describe('Question Editor Node', () => { ) }) + test('Question shows and hides partial scoring toggle correctly', () => { + const props = { + editor: {}, + element: { + content: { type: 'default' }, + children: [ + { type: BREAK_NODE }, + { id: 'mock-mca-id', type: MCASSESSMENT_NODE, content: {} } + ] + } + } + const component = mount() + + // by default the 'partial scoring' checkbox should not be rendered + // make sure there's only one - survey mode + expect(component.find({ type: 'checkbox' }).length).toBe(1) + expect( + component + .find({ type: 'checkbox' }) + .at(0) + .parent() + .props().children[1] + ).toBe('Survey Only') + + // the 'partial scoring' checkbox appears when the question's response type is 'pick-all' + const moddedProps = { ...props } + moddedProps.element.children[1].content = { responseType: 'pick-all' } + component.setProps(moddedProps) + + expect(component.find({ type: 'checkbox' }).length).toBe(2) + expect( + component + .find({ type: 'checkbox' }) + .at(0) + .parent() + .props().children[1] + ).toBe('Partial Scoring') + + moddedProps.element.children[1].content = { responseType: 'pick-one' } + component.setProps(moddedProps) + + expect(component.find({ type: 'checkbox' }).length).toBe(1) + expect( + component + .find({ type: 'checkbox' }) + .at(0) + .parent() + .props().children[1] + ).toBe('Survey Only') + }) + + test('Question toggles partial scoring', () => { + const props = { + editor: {}, + element: { + content: { type: 'default' }, + children: [ + { type: BREAK_NODE }, + { + id: 'mock-mca-id', + type: MCASSESSMENT_NODE, + content: { responseType: 'pick-all' } + } + ] + } + } + const component = mount() + const pathOfMCAssessment = [9, 2, 1] + ReactEditor.findPath.mockReturnValue(pathOfMCAssessment) + + expect(component.find({ type: 'checkbox' }).length).toBe(2) + expect( + component + .find({ type: 'checkbox' }) + .at(0) + .parent() + .props().children[1] + ).toBe('Partial Scoring') + + // turn ON partial scoring + component + .find({ type: 'checkbox' }) + .at(0) + .simulate('change', { target: { checked: true } }) + + component.update() + + expect(Transforms.setNodes).toHaveBeenCalledWith( + props.editor, + { content: { responseType: 'pick-all', partialScoring: true } }, + { at: pathOfMCAssessment } + ) + + Transforms.setNodes.mockClear() + + // turn OFF partial scoring + component + .find({ type: 'checkbox' }) + .at(0) + .simulate('change', { target: { checked: false } }) + + component.update() + + expect(Transforms.setNodes).toHaveBeenCalledWith( + props.editor, + { content: { responseType: 'pick-all', partialScoring: false } }, + { at: pathOfMCAssessment } + ) + }) + + test('Question toggles partial scoring with a solution', () => { + const props = { + editor: {}, + element: { + content: { type: 'default' }, + children: [ + { type: BREAK_NODE }, + { + id: 'mock-mca-id', + type: MCASSESSMENT_NODE, + content: { responseType: 'pick-all' } + }, + { subtype: SOLUTION_NODE } + ] + } + } + const component = mount() + const pathOfMCAssessment = [9, 2, 1] + ReactEditor.findPath.mockReturnValue(pathOfMCAssessment) + + // turn ON partial scoring + component + .find({ type: 'checkbox' }) + .at(0) + .simulate('change', { target: { checked: true } }) + + component.update() + + expect(Transforms.setNodes).toHaveBeenCalledWith( + props.editor, + { content: { responseType: 'pick-all', partialScoring: true } }, + { at: pathOfMCAssessment } + ) + + Transforms.setNodes.mockClear() + + // turn OFF survey + component + .find({ type: 'checkbox' }) + .at(0) + .simulate('change', { target: { checked: false } }) + + component.update() + + expect(Transforms.setNodes).toHaveBeenCalledWith( + props.editor, + { content: { responseType: 'pick-all', partialScoring: false } }, + { at: pathOfMCAssessment } + ) + }) + test('Changing question type updates the assessment node', () => { const props = { editor: {}, @@ -279,7 +461,7 @@ describe('Question Editor Node', () => { content: { type: 'default' }, children: [ { type: BREAK_NODE }, - { id: 'mock-mca-id', type: MCASSESSMENT_NODE }, + { id: 'mock-mca-id', type: MCASSESSMENT_NODE, content: {} }, { subtype: SOLUTION_NODE } ] } @@ -301,7 +483,10 @@ describe('Question Editor Node', () => { editor: {}, element: { content: { type: 'default' }, - children: [{ type: BREAK_NODE }, { id: 'mock-mca-id', type: MCASSESSMENT_NODE }] + children: [ + { type: BREAK_NODE }, + { id: 'mock-mca-id', type: MCASSESSMENT_NODE, content: {} } + ] } } ReactEditor.findPath.mockReturnValue([]) @@ -319,7 +504,14 @@ describe('Question Editor Node', () => { test('Question toggles collapsed status', () => { const mockElement = { content: { type: 'default' }, - children: [{ type: BREAK_NODE }, { id: 'mock-mca-id', type: MCASSESSMENT_NODE }] + children: [ + { type: BREAK_NODE }, + { + id: 'mock-mca-id', + type: MCASSESSMENT_NODE, + content: { partialScoring: false } + } + ] } // intentionally leaving 'collapsed' undefined in element.content to simulate backwards compatibility const props = { diff --git a/packages/obonode/obojobo-chunks-question/empty-node.json b/packages/obonode/obojobo-chunks-question/empty-node.json index 7b1eca324d..5dbe6a1ceb 100644 --- a/packages/obonode/obojobo-chunks-question/empty-node.json +++ b/packages/obonode/obojobo-chunks-question/empty-node.json @@ -22,7 +22,8 @@ "type": "ObojoboDraft.Chunks.MCAssessment", "content": { "responseType": "pick-one", - "shuffle": true + "shuffle": true, + "partialScoring": false }, "children": [ { diff --git a/packages/obonode/obojobo-sections-assessment/components/full-review/__snapshots__/basic-review.test.js.snap b/packages/obonode/obojobo-sections-assessment/components/full-review/__snapshots__/basic-review.test.js.snap index d6dc4ec098..e11b29ce5a 100644 --- a/packages/obonode/obojobo-sections-assessment/components/full-review/__snapshots__/basic-review.test.js.snap +++ b/packages/obonode/obojobo-sections-assessment/components/full-review/__snapshots__/basic-review.test.js.snap @@ -161,6 +161,7 @@ exports[`BasicReview Basic component correct 1`] = ` }, ], "content": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": true, }, @@ -386,6 +387,7 @@ exports[`BasicReview Basic component incorrect 1`] = ` }, ], "content": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": true, }, @@ -611,6 +613,7 @@ exports[`BasicReview Basic component survey 1`] = ` }, ], "content": Object { + "partialScoring": false, "responseType": "pick-one", "shuffle": true, },
+ Students will earn partial credit based on how many of the correct + answers they select. +