Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion packages/multiple-choice/controller/src/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { model, outcome, getScore, createCorrectResponseSession, normalize } from '../index';
import { model, outcome, getScore, createCorrectResponseSession, normalize, validate } from '../index';
import { isResponseCorrect } from '../utils';
import defaults from '../defaults';

Expand Down Expand Up @@ -478,4 +478,45 @@ describe('controller', () => {
expect(sess).toEqual({ ...defaults, ...question, choicesLayout: 'vertical' });
});
});

describe('validate', () => {
const makeChoice = (value, correct = false) => ({ value, label: value, correct });
const config = { minAnswerChoices: 2, maxAnswerChoices: 5 };

it('returns no error when maxSelections >= correctCount', () => {
const m = {
choiceMode: 'checkbox',
maxSelections: 2,
choices: [makeChoice('A', true), makeChoice('B', true), makeChoice('C')],
};
expect(validate(m, config).correctResponse).toBeUndefined();
});

it('returns error when maxSelections < correctCount', () => {
const m = {
choiceMode: 'checkbox',
maxSelections: 1,
choices: [makeChoice('A', true), makeChoice('B', true), makeChoice('C')],
};
expect(validate(m, config).correctResponse).toMatch(/exceeds max selections/);
});

it('does not error in radio mode even if maxSelections < correctCount', () => {
const m = {
choiceMode: 'radio',
maxSelections: 1,
choices: [makeChoice('A', true), makeChoice('B', true), makeChoice('C')],
};
expect(validate(m, config).correctResponse).toBeUndefined();
});

it('does not error when maxSelections is null', () => {
const m = {
choiceMode: 'checkbox',
maxSelections: null,
choices: [makeChoice('A', true), makeChoice('B', true), makeChoice('C')],
};
expect(validate(m, config).correctResponse).toBeUndefined();
});
});
});
8 changes: 8 additions & 0 deletions packages/multiple-choice/controller/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,14 @@ export const validate = (model = {}, config = {}) => {
});

let hasCorrectResponse = false;
let correctCount = 0;

reversedChoices.forEach((choice, index) => {
const { correct, value, label, rationale } = choice;

if (correct) {
hasCorrectResponse = true;
correctCount++;
}

if (!getContent(label)) {
Expand Down Expand Up @@ -278,6 +280,12 @@ export const validate = (model = {}, config = {}) => {

if (!hasCorrectResponse) {
errors.correctResponse = 'No correct response defined.';
} else {
const { maxSelections, choiceMode } = model;

if (choiceMode !== 'radio' && maxSelections != null && correctCount > maxSelections) {
errors.correctResponse = `The number of correct answers (${correctCount}) exceeds max selections (${maxSelections}). Students won't be able to select all correct answers.`;
}
}

if (!isEmpty(choicesErrors)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ import { isComplete } from '../index';

jest.useFakeTimers();
jest.mock('@pie-lib/math-rendering', () => ({ renderMath: jest.fn() }));
jest.mock('@pie-lib/correct-answer-toggle', () => () => null);
jest.mock('@pie-lib/translator', () => ({
__esModule: true,
default: { translator: { t: (key) => key } },
}));
jest.mock('../choice', () => {
const React = require('react');
return {
__esModule: true,
default: ({ choice }) => <div>{choice.label}</div>,
};
});
jest.mock('lodash-es', () => {
const lodash = require('lodash');
return {
Expand Down Expand Up @@ -48,6 +60,16 @@ describe('isComplete', () => {
});
});

beforeAll(() => {
customElements.define('pie-multiple-choice', MultipleChoice);
});

const makeEl = () => {
const el = new MultipleChoice();
el.dispatchEvent = jest.fn();
return el;
};

describe('multiple-choice', () => {
describe('rendering', () => {
const renderComponent = (modelOverrides = {}) => {
Expand All @@ -74,11 +96,6 @@ describe('multiple-choice', () => {
expect(screen.getByTestId('preview-layout')).toBeInTheDocument();
});

it('renders with rationale', () => {
renderComponent({ rationale: 'This is rationale' });
expect(screen.getByText('This is rationale')).toBeInTheDocument();
});

it('renders with teacherInstructions', () => {
renderComponent({ teacherInstructions: 'These are teacher instructions' });
expect(screen.getByText('These are teacher instructions')).toBeInTheDocument();
Expand All @@ -99,56 +116,51 @@ describe('multiple-choice', () => {
describe('events', () => {
describe('model', () => {
it('dispatches model set event', () => {
const el = new MultipleChoice();
el.tagName = 'mc-el';
const el = makeEl();
el.model = {};
expect(el.dispatchEvent).toBeCalledWith(new ModelSetEvent('mc-el', false, true));
expect(el.dispatchEvent).toBeCalledWith(new ModelSetEvent(el.tagName.toLowerCase(), false, true));
});
});

describe('onChange', () => {
it('dispatches session changed event - add answer (checkbox)', () => {
const el = new MultipleChoice();
el.tagName = 'mc-el';
const el = makeEl();
el.model = { choiceMode: 'checkbox' };
el.session = { value: [] };
el._onChange({ value: 'a', selected: true });
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent('mc-el', true));
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent(el.tagName.toLowerCase(), true));
});

it('dispatches session changed event - remove answer (checkbox)', () => {
const el = new MultipleChoice();
el.tagName = 'mc-el';
const el = makeEl();
el.model = { choiceMode: 'checkbox' };
el.session = { value: ['a'] };
el._onChange({ value: 'a', selected: false });
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent('mc-el', false));
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent(el.tagName.toLowerCase(), false));
});

it('dispatches session changed event - add/remove answer (checkbox)', () => {
const el = new MultipleChoice();
el.tagName = 'mc-el';
const el = makeEl();
el.model = { choiceMode: 'checkbox' };
el.session = { value: ['1'] };
el._onChange({ id: '2', selected: true });
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent('mc-el', true));
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent(el.tagName.toLowerCase(), true));

el._onChange({ id: '1', selected: false });
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent('mc-el', true));
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent(el.tagName.toLowerCase(), true));

el._onChange({ id: '2', selected: false });
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent('mc-el', false));
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent(el.tagName.toLowerCase(), false));
});

it('dispatches session changed event - add/change answer (radio)', () => {
const el = new MultipleChoice();
el.tagName = 'mc-el';
const el = makeEl();
el.model = { choiceMode: 'radio' };
el.session = { value: [] };
el._onChange({ value: 'a', selected: true });
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent('mc-el', true));
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent(el.tagName.toLowerCase(), true));
el._onChange({ value: 'b', selected: true });
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent('mc-el', true));
expect(el.dispatchEvent).toBeCalledWith(new SessionChangedEvent(el.tagName.toLowerCase(), true));
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import MultipleChoice from '../multiple-choice';
import MultipleChoice from '../index';

beforeAll(() => {
if (!customElements.get('pie-multiple-choice-key-events')) {
customElements.define('pie-multiple-choice-key-events', MultipleChoice);
}
});

describe('MultipleChoice', () => {
let instance;

beforeEach(() => {
instance = new MultipleChoice();
instance._model = {
mode: 'gather',
choices: [
{ value: '1' },
{ value: '2' },
Expand All @@ -28,6 +35,7 @@ describe('MultipleChoice', () => {
expect(instance._onChange).toHaveBeenCalledWith({
value: '1',
selected: true,
selector: 'Keyboard',
});
});

Expand All @@ -37,6 +45,7 @@ describe('MultipleChoice', () => {
expect(instance._onChange).toHaveBeenCalledWith({
value: '1',
selected: false,
selector: 'Keyboard',
});
});

Expand All @@ -45,6 +54,7 @@ describe('MultipleChoice', () => {
expect(instance._onChange).toHaveBeenCalledWith({
value: '1',
selected: true,
selector: 'Keyboard',
});
});

Expand All @@ -59,6 +69,7 @@ describe('MultipleChoice', () => {
expect(instance._onChange).toHaveBeenCalledWith({
value: '2',
selected: true,
selector: 'Keyboard',
});
});

Expand All @@ -69,6 +80,7 @@ describe('MultipleChoice', () => {
expect(instance._onChange).toHaveBeenCalledWith({
value: '3',
selected: true,
selector: 'Keyboard',
});
});

Expand All @@ -79,6 +91,7 @@ describe('MultipleChoice', () => {
expect(instance._onChange).toHaveBeenCalledWith({
value: '1',
selected: true,
selector: 'Keyboard',
});
});

Expand Down
97 changes: 0 additions & 97 deletions packages/multiple-choice/src/__tests__/multiple-choice-test.jsx

This file was deleted.

Loading
Loading