UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

301 lines (250 loc) • 10.3 kB
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) }) }) })