@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
265 lines (217 loc) • 8.91 kB
JSX
import {vi} from 'vitest'
import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import EssayEdit from '../index'
import runAxeCheck from '@instructure/ui-axe-check'
const merge = (objA, objB) => {
return Object.assign({}, objA, objB)
}
const testRender = (overrideProps, overrideInteractionData) => {
const changeItemStateStub = vi.fn()
const {container} = render(
<EssayEdit
{...merge(
{
changeItemState: changeItemStateStub,
enableRichContentEditor: false, // disable to make testing easier
itemBody: 'STEM',
openImportModal: () => {},
scoringData: {value: 'there better be some text here'},
additionalOptions: [],
interactionData: merge(
{
rce: false,
spellCheck: false,
wordCount: false,
wordLimitEnabled: false,
wordLimitMax: null,
wordLimitMin: null,
notes: '',
},
overrideInteractionData,
),
},
overrideProps,
)}
/>,
)
return {
changeItemStateStub,
container,
}
}
const assertNewAttrEqual = (changeItemStateStub, attr, expectedVal) => {
expect(changeItemStateStub).toHaveBeenCalled()
const newVal = changeItemStateStub.mock.lastCall[0].interactionData[attr]
expect(newVal).toBe(expectedVal)
}
describe('Essay Edit', () => {
let defaultProps = {}
let changeItemStateStub
beforeEach(() => {
changeItemStateStub = vi.fn()
defaultProps = {
...merge({
changeItemState: changeItemStateStub,
enableRichContentEditor: false, // disable to make testing easier
itemBody: 'STEM',
openImportModal: () => {},
scoringData: {value: 'there better be some text here'},
additionalOptions: [],
interactionData: merge({
rce: false,
spellCheck: false,
wordCount: false,
wordLimitEnabled: false,
wordLimitMax: null,
wordLimitMin: null,
notes: '',
}),
}),
}
})
describe('rendering', () => {
it('renders a basic essay', () => {
const {container} = render(<EssayEdit {...defaultProps} enableRichContentEditor />)
// Check the input fields
expect(container.querySelector('[data-automation="sdk-question-stem"]')).toBeInTheDocument()
expect(screen.queryByRole('textbox', {name: /rich content editor/i})).toBeInTheDocument()
// Check the tabs
expect(screen.queryByRole('button', {name: /options/i})).toBeInTheDocument()
expect(screen.queryByRole('button', {name: /grading notes/i})).toBeInTheDocument()
// Check the word limit inputs
expect(screen.queryByRole('textbox', {name: /Minimum/i})).not.toBeInTheDocument()
expect(screen.queryByRole('textbox', {name: /Maximum/i})).not.toBeInTheDocument()
})
it('renders with the rce disabled', () => {
const {container} = testRender({enableRichContentEditor: false})
expect(container).toBeDefined()
const rces = container.querySelectorAll('[data-automation^="sdk-rce"]')
expect(rces).toHaveLength(0)
})
it('renders word limit inputs', () => {
testRender(
{},
{
wordLimitEnabled: true,
wordLimitMin: 0,
wordLimitMax: 0,
},
)
const minInput = screen.getByLabelText(/Minimum/i)
expect(minInput).toBeDefined()
expect(minInput.value).toBe('0')
const maxInput = screen.getByLabelText(/Maximum/i)
expect(maxInput).toBeDefined()
expect(maxInput.value).toBe('0')
})
it('overrideEditableForRegrading disables a bunch of things', () => {
const {container} = render(<EssayEdit {...defaultProps} overrideEditableForRegrading />)
const questionStemTextArea = container.querySelector(
'[data-automation="sdk-question-stem"] textarea',
)
expect(questionStemTextArea.getAttribute('disabled')).not.toBeNull()
const checkboxes = screen.queryAllByRole('checkbox')
for (const checkbox of checkboxes) {
expect(checkbox.getAttribute('disabled')).not.toBeNull()
}
const gradingNotesTab = screen.getByRole('button', {name: /grading notes/i})
userEvent.click(gradingNotesTab)
const gradingNotesTextArea = screen.getByRole('textbox', {name: /grading notes/i})
expect(gradingNotesTextArea.getAttribute('disabled')).not.toBeNull()
})
})
describe('#changeItemState', () => {
it('handles changes to itemBody', () => {
const {changeItemStateStub, container} = testRender()
const stem = container.querySelector('[data-automation="sdk-question-stem"] textarea')
expect(stem).not.toBeNull()
fireEvent.change(stem, {target: {value: 'new stem val'}})
expect(changeItemStateStub).toHaveBeenCalled()
expect(changeItemStateStub.mock.calls[0][0].itemBody).toBe('new stem val')
})
it('saves the updated notes with changeItemState', () => {
render(<EssayEdit {...defaultProps} />)
const gradingNotesTab = screen.getByRole('button', {name: /grading notes/i})
userEvent.click(gradingNotesTab)
const gradingNotesTextArea = screen.getByRole('textbox', {name: /grading notes/i})
userEvent.clear(gradingNotesTextArea)
userEvent.type(gradingNotesTextArea, 'new val')
expect(changeItemStateStub).toHaveBeenCalledWith({scoringData: {value: 'new val'}})
})
// TODO: QUIZ-XXXXX — migrate "handles all the toggles" test
// This test relies on @instructure/ui-test-utils' wrapper .label property
// which maps checkbox labels to interactionData keys. Needs RTL migration.
it.skip('handles all the toggles', () => {})
it('handles minimum word limit changes', () => {
const {changeItemStateStub} = testRender({}, {wordLimitEnabled: true})
const minInput = screen.getByLabelText(/Minimum/i)
expect(minInput).toBeDefined()
fireEvent.change(minInput, {target: {value: '10'}})
fireEvent.blur(minInput)
assertNewAttrEqual(changeItemStateStub, 'wordLimitMin', '10')
// make sure blank sets it to null
fireEvent.change(minInput, {target: {value: ''}})
fireEvent.blur(minInput)
assertNewAttrEqual(changeItemStateStub, 'wordLimitMin', null)
})
it('handles maximum word limit changes', () => {
const {changeItemStateStub} = testRender({}, {wordLimitEnabled: true})
const maxInput = screen.getByLabelText(/Maximum/i)
expect(maxInput).toBeDefined()
fireEvent.change(maxInput, {target: {value: '100'}})
fireEvent.blur(maxInput)
assertNewAttrEqual(changeItemStateStub, 'wordLimitMax', '100')
// make sure blank sets it to null
fireEvent.change(maxInput, {target: {value: ''}})
fireEvent.blur(maxInput)
assertNewAttrEqual(changeItemStateStub, 'wordLimitMax', null)
})
})
describe('calculator per question option', () => {
it('does not render the calculator checkbox if showCalculatorOption is false', () => {
render(<EssayEdit {...defaultProps} showCalculatorOption={false} />)
expect(screen.queryByRole('checkbox', {name: /show on-screen calculator/i})).toBeNull()
})
it('shows with basic option selected', () => {
testRender({calculatorType: 'basic'})
const calculatorBasicRadioInput = screen.getByRole('radio', {name: /basic calculator/i})
const calculatorScientificRadioInput = screen.getByRole('radio', {
name: /scientific calculator/i,
})
expect(calculatorBasicRadioInput.checked).toBe(true)
expect(calculatorScientificRadioInput.checked).toBe(false)
})
it('switches from none to basic calculator', () => {
const {changeItemStateStub} = testRender({calculatorType: 'none'})
const calculatorCheckbox = screen.getByRole('checkbox', {
name: /show on-screen calculator/i,
})
userEvent.click(calculatorCheckbox)
expect(changeItemStateStub).toHaveBeenCalled()
expect(changeItemStateStub.mock.lastCall[0].calculatorType).toBe('basic')
})
it('enables one question at a time properly', () => {
const setOQAATStub = vi.fn()
testRender({
calculatorType: 'basic',
oneQuestionAtATime: false,
setOneQuestionAtATime: setOQAATStub,
})
const toggle = screen.getByRole('checkbox', {name: /enable one question at a time/i})
userEvent.click(toggle)
expect(setOQAATStub).toHaveBeenCalledTimes(1)
})
})
describe('a11y tests', () => {
it('should meet a11y standards', async () => {
testRender()
expect(
await runAxeCheck(document.body, {
ignores: ['region'],
}),
).toBe(true)
})
})
})