UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

315 lines (270 loc) • 9.87 kB
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') }) }) })