@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
315 lines (270 loc) • 9.87 kB
JavaScript
import React from 'react'
import {expect} from '@instructure/ui-test-utils'
import {renderWithDnd} from '../../../../../tests/util/dndTestUtils'
import RichFillBlankTake from '../index'
import {screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
const firstBlankId = 'firstBlankId'
const firstStemItemId = 'firstStemItemId'
const secondStemItemId = 'secondStemItemId'
const thirdStemItemId = 'thirdStemItemId'
const handleResponseUpdateStub = vi.fn()
const props = {
handleResponseUpdate: handleResponseUpdateStub,
userResponse: {value: [{id: firstBlankId, value: 'stem', type: 'Text'}]},
itemBody: `<p>This is the <span id="blank_${firstBlankId}"></span> value.<p>`,
interactionData: {
stemItems: [
{id: firstStemItemId, position: 1, type: 'text', value: 'This is the '},
{id: secondStemItemId, position: 2, type: 'blank', blankId: firstBlankId},
{id: thirdStemItemId, position: 3, type: 'text', value: ' value.'},
],
blanks: [{id: firstBlankId, answerType: 'openEntry'}],
},
}
const newprops = {
...props,
interactionData: {
...props.interactionData,
blanks: [
{
id: firstBlankId,
answerType: 'wordbank',
choices: [
{id: 'choice_uuid11_brazil', position: 1, itemBody: 'Brazil'},
{id: 'choice_uuid12_austria', position: 2, itemBody: 'Austria'},
{id: 'choice_uuid13_america', position: 3, itemBody: 'America'},
],
},
],
},
userResponse: {
value: [
{
id: firstBlankId,
value: 'choice_uuid12_austria',
type: 'Text',
},
],
},
}
describe('Rich Fill in the Blank Item Take', () => {
afterEach(() => {
handleResponseUpdateStub.mockClear()
})
describe('rendering', () => {
it('renders the component without crashing', () => {
expect(() => {
renderWithDnd(<RichFillBlankTake {...props} />)
}).to.not.throw()
})
it('renders a SR message enumerating the blanks', () => {
renderWithDnd(<RichFillBlankTake {...props} />)
const srText = screen.getByText('Question Blank 1 of 1')
expect(srText).to.exist()
})
})
describe('onChange', () => {
it('calls handleResponseUpdate with correct args', () => {
renderWithDnd(<RichFillBlankTake {...props} />)
const blankInput = screen.getByPlaceholderText(/type your answer.../i)
userEvent.clear(blankInput)
userEvent.type(blankInput, 'new value')
globalThis.expect(handleResponseUpdateStub).toHaveBeenCalled()
const lastCall = handleResponseUpdateStub.mock.lastCall[0]
expect(lastCall[0]).to.deep.include({
id: firstBlankId,
value: 'new value',
type: 'Text',
})
})
})
describe('wordbank type', () => {
it('renders the wordbank', () => {
renderWithDnd(<RichFillBlankTake {...newprops} />)
// Wordbank should render with available choices
const brazilChoice = screen.getByText('Brazil')
const americaChoice = screen.getByText('America')
const austriaChoice = screen.getByText('Austria')
expect(brazilChoice).to.exist()
expect(americaChoice).to.exist()
expect(austriaChoice).to.exist()
})
describe('wordBankChoices', () => {
it('returns a choice for each choice not in the response of a wordbank blank', () => {
renderWithDnd(<RichFillBlankTake {...newprops} />)
// 3 total choices, 1 used (Austria), so 2 should be available
const brazilChoice = screen.getByText('Brazil')
const americaChoice = screen.getByText('America')
expect(brazilChoice).to.exist()
expect(americaChoice).to.exist()
})
describe('using common wordbank', () => {
it('uses wordBankChoices when present', () => {
renderWithDnd(
<RichFillBlankTake
{...newprops}
interactionData={{
...newprops.interactionData,
wordBankChoices: [
{id: '1', itemBody: 'foo'},
{id: '2', itemBody: 'bar'},
],
}}
/>,
)
// Both choices should be visible
expect(screen.getByText('foo')).to.exist()
expect(screen.getByText('bar')).to.exist()
})
describe('when reuseWordBankChoices is true', () => {
it('filters duplicates', () => {
renderWithDnd(
<RichFillBlankTake
{...newprops}
interactionData={{
...newprops.interactionData,
wordBankChoices: [
{id: '1', itemBody: 'foo'},
{id: '2', itemBody: 'foo'},
],
reuseWordBankChoices: true,
}}
/>,
)
// Should only show one 'foo' (duplicates filtered)
const fooElements = screen.getAllByText('foo')
expect(fooElements.length).to.equal(1)
})
it('continues to show choices which are already used', () => {
renderWithDnd(
<RichFillBlankTake
{...newprops}
interactionData={{
...newprops.interactionData,
wordBankChoices: [
{id: '1', itemBody: 'foo'},
{id: '2', itemBody: 'bar'},
],
reuseWordBankChoices: true,
}}
userResponse={{
value: [
{
id: firstBlankId,
value: 'foo',
type: 'Text',
},
],
}}
/>,
)
// Both choices should still be available (reuseWordBankChoices allows reuse)
expect(screen.getAllByText('foo')).to.exist()
expect(screen.getAllByText('bar')).to.exist()
})
})
describe('when reuseWordBankChoices is false', () => {
it('renders duplicates', () => {
renderWithDnd(
<RichFillBlankTake
{...newprops}
interactionData={{
...newprops.interactionData,
wordBankChoices: [
{id: '1', itemBody: 'foo'},
{id: '2', itemBody: 'foo'},
],
reuseWordBankChoices: false,
}}
/>,
)
// Should show both 'foo' items (no deduplication)
const fooElements = screen.getAllByText('foo')
expect(fooElements.length).to.equal(2)
})
it('filters choices which are already used', () => {
renderWithDnd(
<RichFillBlankTake
{...newprops}
interactionData={{
...newprops.interactionData,
wordBankChoices: [
{id: '1', itemBody: 'foo'},
{id: '2', itemBody: 'bar'},
],
reuseWordBankChoices: false,
}}
userResponse={{
value: [
{
id: firstBlankId,
value: 'foo',
type: 'Text',
},
],
}}
/>,
)
const allFooElements = screen.getAllByText('foo')
expect(allFooElements.length).to.equal(1) // One in the input value
})
})
})
})
})
describe('openEntry', () => {
it('trims whitespace around answers', () => {
renderWithDnd(<RichFillBlankTake {...props} />)
const blankInput = screen.getByPlaceholderText(/type your answer.../i)
userEvent.clear(blankInput)
userEvent.type(blankInput, ' hello world ')
globalThis.expect(handleResponseUpdateStub).toHaveBeenCalledWith([
{
id: firstBlankId,
value: 'hello world',
type: 'Text',
},
])
})
})
describe('accessibility', () => {
it('provides screen reader text for blank positions', () => {
renderWithDnd(<RichFillBlankTake {...props} />)
const srText = screen.getByText('Question Blank 1 of 1')
expect(srText).to.exist()
})
it('has autoComplete="off" for openEntry inputs', () => {
renderWithDnd(<RichFillBlankTake {...props} />)
const input = screen.getByPlaceholderText(/type your answer.../i)
expect(input).to.have.attribute('autocomplete', 'off')
})
})
describe('interaction states', () => {
it('renders inputs as enabled by default', () => {
renderWithDnd(<RichFillBlankTake {...props} />)
const input = screen.getByPlaceholderText(/type your answer.../i)
expect(input).to.not.have.attribute('readonly')
expect(input).to.not.have.attribute('disabled')
})
it('renders inputs as readonly when readOnly prop is true', () => {
renderWithDnd(<RichFillBlankTake {...props} readOnly={true} />)
const input = screen.getByDisplayValue('stem')
expect(input).to.have.attribute('readonly')
})
it('updates openEntry value when readOnly', () => {
const {rerender} = renderWithDnd(<RichFillBlankTake {...props} readOnly={true} />)
let input = screen.getByDisplayValue('stem')
expect(input.value).to.equal('stem')
rerender(
<RichFillBlankTake
{...props}
readOnly={true}
userResponse={{value: [{id: firstBlankId, value: 'new value', type: 'Text'}]}}
/>,
)
input = screen.getByDisplayValue('new value')
expect(input.value).to.equal('new value')
})
})
})