@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
301 lines (250 loc) • 10.3 kB
JavaScript
import React from 'react'
import {render, screen} from '@testing-library/react'
import {vi} from 'vitest'
import shouldRenderComponent from '../../../../../tests/util/render-checks'
import ReactDOM from 'react-dom'
import HotspotEdit from '../index'
import QuestionSettingsContainer from '../../../common/edit/components/QuestionSettingsContainer'
import QuestionContainer from '../../../common/edit/components/QuestionContainer'
import RemoveChoiceButton from '../../../common/edit/components/RemoveChoiceButton'
import enableRichContentEditorChecks from '../../../../../tests/util/enableRichContentEditorChecks'
import shouldValidateItemChanges from '../../../../../tests/util/shouldValidateItemChanges'
import CalculatorOptionWithOqaatAlert from '../../../common/edit/components/CalculatorOptionWithOqaatAlert'
import {ReactTestbed} from '../../../../../tests/util/react-testbed'
const changeItemStateStub = vi.fn()
const mediaUploadStub = vi.fn()
const setOneQuestionAtATimeStub = vi.fn()
const props = {
itemBody: 'Which of these players is David Ferrer',
interactionData: {
imageUrl: 'https://i.ytimg.com/vi/LgkAebhJ7iE/maxresdefault.jpg',
},
openImportModal: () => {},
scoringData: {
value: [
{
type: 'square',
coordinates: [
{x: 0.1, y: 0.13},
{x: 0.07, y: 0.14},
],
id: 1,
},
],
},
changeItemState: changeItemStateStub,
mediaUpload: mediaUploadStub,
setOneQuestionAtATime: setOneQuestionAtATimeStub,
multipleHotSpotEnabled: true,
}
describe('Hotspot Edit', () => {
const testbed = new ReactTestbed(HotspotEdit, props)
afterEach(() => {
changeItemStateStub.mockClear()
mediaUploadStub.mockClear()
})
shouldRenderComponent(testbed, {}, HotspotEdit)
shouldValidateItemChanges(testbed)
enableRichContentEditorChecks(testbed)
describe('rendering', () => {
describe('QuestionContainer', () => {
it('renders a question container', () => {
testbed.render()
const items = testbed.findChildrenByType(QuestionContainer)
expect(items.length).toBe(1)
})
it('calls changeItemState with the correct arguments', () => {
testbed.render({changeItemState: changeItemStateStub})
const questionCont = testbed.findChildrenByType(QuestionContainer)[0]
questionCont.props.onDescriptionChange('new stem val')
const newBody = changeItemStateStub.mock.lastCall[0].itemBody
expect(newBody).toBe('new stem val')
})
})
describe('accessibilityInfo', () => {
it('renders non-a11y reminder', () => {
testbed.render()
const items = testbed.findChildrenByClass('accessibilityInfo')
expect(items.length).toBe(1)
})
})
describe('overrideEditableForRegrading', () => {
it('disables question stem input', () => {
testbed.render({overrideEditableForRegrading: true})
expect(testbed.findChildByType(QuestionContainer).props.disabled).toBe(true)
})
it('disables the calculator option', () => {
testbed.render({overrideEditableForRegrading: true})
expect(testbed.findChildByType(CalculatorOptionWithOqaatAlert).props.disabled).toBe(true)
})
})
describe('QuestionConfigContainer', () => {
it('renders an QuestionSettingsContainer', () => {
testbed.render()
const items = testbed.findChildrenByType(QuestionSettingsContainer)
expect(items.length).toBe(1)
})
})
describe('Footer', () => {
it('renders footer', () => {
testbed.render()
const removeButton = testbed.findChildrenByType(RemoveChoiceButton)
expect(removeButton.length).toBe(1)
expect(testbed.getText()).toContain('Square Hot Spot')
expect(testbed.getText()).toContain('Clicks within the hot spot will be considered correct')
})
it('does not render footer if there is no url', () => {
testbed.render({
interactionData: {imageUrl: null},
scoringData: {value: []},
})
const removeButton = testbed.findChildrenByType(RemoveChoiceButton)
expect(removeButton.length).toBe(0)
expect(testbed.getText()).not.toContain('Square Hot Spot')
expect(testbed.getText()).not.toContain(
'Clicks within the hot spot will be considered correct',
)
})
})
})
describe('Helpers', () => {
describe('#convertCoordinates', () => {
it('correctly converts and updates coordinates when drawing a new hotspot', () => {
testbed.render()
const newState = {
imageWidth: 700,
imageHeight: 500,
}
const subject = testbed.findChildrenByType(HotspotEdit.WrappedComponent).pop()
subject.drawingContainer.currentImageWidth = () => {
return newState.imageWidth
}
subject.drawingContainer.currentImageHeight = () => {
return newState.imageHeight
}
subject.setState({
currentId: 1,
currentShapeType: 'square',
tempHotspot: null,
})
const oldCoordinates = [{x: 70, y: 70}]
subject.convertCoordinates(oldCoordinates, true)
const newCoordinates = changeItemStateStub.mock.lastCall[0].scoringData.value[0].coordinates
expect(newCoordinates.length).toBe(1)
expect(newCoordinates[0].x).toBe(oldCoordinates[0].x / newState.imageWidth)
expect(newCoordinates[0].y).toBe(oldCoordinates[0].y / newState.imageHeight)
})
it('correctly finalizes and prepares new tempHotspot when not drawing', () => {
testbed.render()
const newState = {
imageWidth: 700,
imageHeight: 500,
}
const subject = testbed.findChildrenByType(HotspotEdit.WrappedComponent).pop()
subject.drawingContainer.currentImageWidth = () => {
return newState.imageWidth
}
subject.drawingContainer.currentImageHeight = () => {
return newState.imageHeight
}
subject.setState({
currentId: 1,
currentShapeType: 'square',
})
const oldCoordinates = [{x: 140, y: 100}]
subject.convertCoordinates(oldCoordinates, false) // isDrawing = false
const newCoordinates = changeItemStateStub.mock.lastCall[0].scoringData.value[0].coordinates
expect(newCoordinates.length).toBe(1)
expect(newCoordinates[0].x).toBe(oldCoordinates[0].x / newState.imageWidth)
expect(newCoordinates[0].y).toBe(oldCoordinates[0].y / newState.imageHeight)
// Ensure a new tempHotspot is prepared for the next shape
const newTempHotspot = subject.state.tempHotspot
expect(newTempHotspot).toEqual({type: 'square', coordinates: [], id: 2})
})
})
})
describe('Actions', () => {
describe('#onRemoveDrawing', () => {
it('calls changeItemState with empty coordinates array', () => {
testbed.render()
const button = ReactDOM.findDOMNode(testbed.findChildByType(RemoveChoiceButton).button)
button.click()
const newStateCoordinates =
changeItemStateStub.mock.lastCall[0].scoringData.value[0].coordinates
expect(newStateCoordinates.length).toBe(0)
})
})
describe('#handleRemoveImage', () => {
it('calls changeItemState to clear the image URL and coordinates', () => {
testbed.render()
const subject = testbed.findChildrenByType(HotspotEdit.WrappedComponent).pop()
subject.handleRemoveImage()
const newState = changeItemStateStub.mock.lastCall[0]
expect(newState.interactionData.imageUrl).toBeUndefined()
expect(newState.scoringData.value).toEqual([])
})
})
describe('#handleDropAccepted', () => {
it('calls changeItemState with the correct arguments', () => {
testbed.render()
const blob = new Blob([''], {type: 'text/html'})
blob.lastModifiedDate = ''
blob.name = 'name.png'
const subject = testbed.findChildrenByType(HotspotEdit.WrappedComponent).pop()
subject.handleDropAccepted([blob])
expect(mediaUploadStub).toHaveBeenCalledTimes(1)
expect(mediaUploadStub.mock.calls[0][0]).toBe(blob)
const cb = mediaUploadStub.mock.calls[0][1]
expect(typeof cb).toBe('function')
cb('url')
expect(changeItemStateStub).toHaveBeenCalled()
expect(changeItemStateStub.mock.lastCall[0]).toEqual(
expect.objectContaining({
interactionData: {
imageUrl: 'url',
hotspotsCount: 0,
},
scoringData: {
value: [{coordinates: [], type: 'square', id: 1}],
},
}),
)
return Promise.resolve()
})
})
})
describe('a11y tests', () => {
it('should meet a11y standards', async () => {
testbed.render()
await testbed.checkA11yStandards()
})
})
describe('calculator per question option', () => {
it('does not render the options if showCalculatorOption is false', () => {
render(<HotspotEdit {...props} showCalculatorOption={false} />)
expect(screen.queryByRole('button', {name: /options/i})).toBeNull()
})
it('renders the calculator per question option', () => {
testbed.render()
const items = testbed.findChildrenByType(CalculatorOptionWithOqaatAlert)
expect(items).toHaveLength(1)
})
it('calls the correct callback function with the correct data when the calculator type is changed', () => {
const value = 'basic'
testbed.render()
const items = testbed.findChildrenByType(CalculatorOptionWithOqaatAlert)
items[0].props.onCalculatorTypeChange(null, value)
expect(changeItemStateStub).toHaveBeenCalledWith(
expect.objectContaining({
calculatorType: value,
}),
)
})
it('calls the correct callback function with the correct data when OQAAT is changed', () => {
testbed.render()
const items = testbed.findChildrenByType(CalculatorOptionWithOqaatAlert)
items[0].props.onOqaatChange(true)
expect(setOneQuestionAtATimeStub).toHaveBeenCalledWith(true)
})
})
})