@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
199 lines (176 loc) • 6.77 kB
JavaScript
import React from 'react'
import {render, screen} from '@testing-library/react'
import runAxeCheck from '@instructure/ui-axe-check'
import reduce from 'lodash/fp/reduce'
import zipAll from 'lodash/fp/zipAll'
import {combination, baseNCombination, cartesianProduct} from '../../../../util/combinatorics'
import MultipleAnswerResult from '../index'
const props = {
itemBody: 'Who was in the first cabinet of the USA?',
interactionData: {
choices: [
{id: 'uuid1', position: 1, itemBody: 'Thomas Jefferson'},
{id: 'uuid2', position: 2, itemBody: 'John Marshall'},
{id: 'uuid3', position: 3, itemBody: 'John Knox'},
{id: 'uuid4', position: 4, itemBody: 'Alexander Hamilton'},
{id: 'uuid5', position: 5, itemBody: 'Aaron Burr'},
{id: 'uuid6', position: 6, itemBody: 'Ben Franklin'},
],
},
scoredData: {
correct: false,
value: {
uuid1: {resultScore: 1, userResponded: true},
uuid3: {resultScore: 1, userResponded: false},
uuid4: {resultScore: 1, userResponded: false},
},
},
}
// generate all possible scoredData tuples
// with all combinations of userResponded (true or false or undefined) and resultScore (1 or 0 or undefined)
function generateScoredDataTuples(choiceIds) {
return cartesianProduct(
combination([0, 1, void 0], choiceIds.length),
baseNCombination([true, false, void 0], choiceIds.length),
).map(tuple => zipAll([choiceIds, ...tuple]))
}
describe('Multiple Answer Result', () => {
it('should render', () => {
const {container} = render(<MultipleAnswerResult {...props} />)
expect(container.firstChild).toBeInTheDocument()
})
// TODO: this is really hard to follow. Simplify?
generateScoredDataTuples(['uuid1', 'uuid2', 'uuid3']).forEach(tuples => {
describe(`for scoredData tuple ${JSON.stringify(tuples)}`, () => {
const scoredData = {}
tuples.forEach(([id, resultScore, userResponded]) => {
scoredData[id] = {resultScore, userResponded}
})
it('should render checkboxes properly', () => {
const {container} = render(
<MultipleAnswerResult
{...props}
scoredData={{
value: scoredData,
}}
/>,
)
const checkboxes = container.querySelectorAll('input[type="checkbox"]')
tuples.forEach(([_, __, userResponded], index) => {
expect(checkboxes[index].checked).toBe(!!userResponded)
})
})
// Count the number of items in these tuples that have enough data to present a FeedbackWrapper
// if it isn't listed correct/incorrect or as a user response, it will be rendered plainly
let feedbackNumber = reduce(
(result, [_, resultScore, userResponded]) => {
if (typeof resultScore !== 'undefined' && typeof userResponded !== 'undefined') {
return result + 1
}
return result
},
0,
tuples,
)
if (tuples.every(([_, __, userResponded]) => !userResponded)) {
feedbackNumber += 1 // the "no answer" message
}
it(`should render ${feedbackNumber} FeedbackWrappers`, () => {
const {container} = render(
<MultipleAnswerResult
{...props}
scoredData={{
value: scoredData,
}}
/>,
)
// FeedbackWrapper renders with className="fs-mask"
const feedbackWrappers = container.querySelectorAll('.fs-mask')
expect(feedbackWrappers).toHaveLength(feedbackNumber)
})
})
})
describe('answer feedback', () => {
it('displays answer feedback for selected choices', () => {
const propsWithFeedback = {
...props,
MultipleAnswerFeedback: {
uuid1: '<p>Great choice! This is correct.</p>',
uuid2: '<p>Not quite right.</p>',
uuid3: '<p>Good job!</p>',
},
scoredData: {
correct: false,
value: {
uuid1: {resultScore: 1, userResponded: true},
uuid2: {resultScore: 0, userResponded: true},
uuid3: {resultScore: 1, userResponded: false},
},
},
}
const {container} = render(<MultipleAnswerResult {...propsWithFeedback} />)
const feedbackDisplays = container.querySelectorAll(
'[data-automation="sdk-answer-feedback-display"]',
)
// Should show feedback for uuid1 and uuid2 (selected), but not uuid3 (not selected)
expect(feedbackDisplays.length).toBe(2)
expect(screen.getByText('Great choice! This is correct.')).toBeInTheDocument()
expect(screen.getByText('Not quite right.')).toBeInTheDocument()
expect(screen.queryByText('Good job!')).toBeNull()
})
it('does not display feedback when no feedback is provided', () => {
const {container} = render(<MultipleAnswerResult {...props} MultipleAnswerFeedback={{}} />)
const feedbackDisplays = container.querySelectorAll(
'[data-automation="sdk-answer-feedback-display"]',
)
expect(feedbackDisplays.length).toBe(0)
})
it('does not display feedback for unselected choices', () => {
const propsWithFeedback = {
...props,
MultipleAnswerFeedback: {
uuid1: '<p>Feedback for uuid1</p>',
uuid2: '<p>Feedback for uuid2</p>',
},
scoredData: {
correct: false,
value: {
uuid1: {resultScore: 1, userResponded: false},
uuid2: {resultScore: 0, userResponded: false},
},
},
}
const {container} = render(<MultipleAnswerResult {...propsWithFeedback} />)
const feedbackDisplays = container.querySelectorAll(
'[data-automation="sdk-answer-feedback-display"]',
)
// No feedback should be displayed since no choices were selected
expect(feedbackDisplays.length).toBe(0)
})
it('displays "Feedback" heading for each answer feedback', () => {
const propsWithFeedback = {
...props,
MultipleAnswerFeedback: {
uuid1: '<p>Great choice!</p>',
},
scoredData: {
correct: true,
value: {
uuid1: {resultScore: 1, userResponded: true},
},
},
}
render(<MultipleAnswerResult {...propsWithFeedback} />)
// Should display the "Feedback" heading
const feedbackHeadings = screen.getAllByText('Feedback')
expect(feedbackHeadings.length).toBeGreaterThan(0)
})
})
describe('a11y tests', () => {
it('should meet a11y standards', async () => {
const {container} = render(<MultipleAnswerResult {...props} />)
const result = await runAxeCheck(container)
expect(result).toBe(true)
})
})
})