UNPKG

@instructure/quiz-grading

Version:

The Quiz React SDK by Instructure Inc.

335 lines (296 loc) • 9.27 kB
import React from 'react' import {QuizEntry} from '@instructure/quiz-core/common/records/QuizEntry' import {SessionItem} from '@instructure/quiz-core/common/records/SessionItem' import {describe, it, expect, beforeEach, vi} from 'vitest' import {render, screen} from '../../../../../../tests/utils/rtlRenderOverride' import { OPEN_MODAL, REGRADE_MODAL, SHOW_ERROR, REGRADE_SET_PAYLOAD, HIDE_ERROR, } from '@instructure/quiz-common/constants' import userEvent from '@testing-library/user-event' import {RegradeOverride} from '../index' import {fromJS, Iterable} from 'immutable' describe('Regrade Override', () => { let dispatchStub = vi.fn() const quizId = '22' const quizEntryId = '222' const regradedQuizEntryId = '223' const sessionItem = new SessionItem({ id: '333', quizEntryId, }) const regradedSessionItem = new SessionItem({ id: '334', quizEntryId: regradedQuizEntryId, }) const scoreReconciliation = 'new_score' const itemId = '100' const otherItemId = '200' const invalidItemId = '300' const scoringAlgorithm = 'Equivalence' const pointsPossible = 1.0 const regradeAdjustedPointsPossible = 5.0 const regradeAdjustedScoringData = {value: 'regraded'} const handleValidationErrorsFromApi = Function.prototype // The props below are to avoid prop type error const clearValidationErrorsFromApi = Function.prototype const displayPosition = 1 const parentId = '1' const clearNextQuizEntry = Function.prototype const createTempItem = Function.prototype const guid = '1' const parentType = 'div' const createTempStimulus = Function.prototype const addContentPopoverOpen = false const onCancel = Function.prototype const scoringData = { value: 'true', } // Add some modifications const newScoringData = {value: 'false'} const newPointsPossible = 14.0 const defaultState = { regrading: { quizEntryId, scoreReconciliation, regradingPayload: { quizEntryId: '223', scoringAlgorithm: 'someAlgorithm', scoringData, }, }, items: { [itemId]: { id: itemId, interactionType: '1', scoringAlgorithm, scoringData, }, [otherItemId]: { id: otherItemId, interactionType: '1', scoringAlgorithm, scoringData, }, [invalidItemId]: { id: invalidItemId, interactionType: '1', scoringAlgorithm, scoringData, errors: {scoringData: {value: ['Answer must be a number']}}, }, }, modifications: { items: { [itemId]: {id: itemId, scoringData: newScoringData}, [invalidItemId]: { id: invalidItemId, scoringData: newScoringData, errors: {scoringData: {value: ['Answer must be a number']}}, }, }, quizEntries: {[quizEntryId]: {pointsPossible: newPointsPossible}}, }, quizzes: { activeQuizId: quizId, [quizId]: { id: quizId, oneAtATimeType: 'question', }, }, quizEntries: { [quizEntryId]: { entry: { id: itemId, schema: 'Item', }, entryType: 'Item', id: quizEntryId, pointsPossible, }, [regradedQuizEntryId]: { entry: { id: otherItemId, schema: 'Item', }, entryType: 'Item', id: regradedQuizEntryId, pointsPossible, regradeAdjustedPointsPossible, regradeAdjustedScoringData, }, }, ui: { appContainer: '.root', }, } const defaultProps = { addContentPopoverOpen, clearNextQuizEntry, createTempItem, createTempStimulus, displayPosition, guid, itemId, onCancel, parentId, parentType, quizSessionId: '333', sessionItem, handleValidationErrorsFromApi, clearValidationErrorsFromApi, regradingPayload: { quizEntryId: '222', scoringAlgorithm: 'someAlgorithm', scoringData, }, } let storeOptions beforeEach(() => { dispatchStub = vi.fn() storeOptions = {state: {...defaultState, dispatchStub}} vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() }) it('renders interaction in overrideEditableForRegrading mode', () => { render(<RegradeOverride {...defaultProps} />, {storeOptions}) expect(screen.getByRole('button', {name: /options/i})).toBeInTheDocument() expect(screen.getByRole('checkbox', {name: /show on-screen calculator/i})).toBeDisabled() }) it('passes the correct oneQuestionAtATime value to the interaction', () => { render(<RegradeOverride {...defaultProps} />, { storeOptions: { state: { ...storeOptions.state, items: { ...storeOptions.state.items, [itemId]: { ...storeOptions.state.items[itemId], calculatorType: 'basic', }, }, }, }, }) expect( screen.getByRole('checkbox', {name: /enable one question at a time/i}), ).toBeInTheDocument() }) it('renders the alert messaging', () => { render(<RegradeOverride {...defaultProps} />, {storeOptions}) expect(screen.getByText(/you are regrading this question/i)).toBeInTheDocument() }) it('renders the action buttons', async () => { render(<RegradeOverride {...defaultProps} />, {storeOptions}) const cancelButton = screen.getByRole('button', {name: /cancel/i}) const regradeButton = screen.getByRole('button', {name: /regrade/i}) expect(cancelButton).toBeInTheDocument() expect(regradeButton).toBeInTheDocument() expect(regradeButton).toHaveTextContent('Regrade question 0') }) it('cancels modifications when the cancel button is pressed', async () => { const onCancelStub = vi.fn() render(<RegradeOverride {...defaultProps} onCancel={onCancelStub} />, {storeOptions}) const cancelButton = await screen.findByRole('button', {name: /cancel/i}) userEvent.click(cancelButton) expect(onCancelStub).toHaveBeenCalled() }) it('opens the modal when regrading', async () => { render(<RegradeOverride {...defaultProps} />, {storeOptions}) const regradeButton = await screen.findByRole('button', {name: /regrade/i}) userEvent.click(regradeButton) expect(dispatchStub).toHaveBeenCalledWith({ payload: {key: '1'}, type: HIDE_ERROR, }) expect(dispatchStub).toHaveBeenCalledWith({ payload: { options: {}, title: REGRADE_MODAL, }, type: OPEN_MODAL, }) }) it('does not open the modal with invalid input', async () => { render(<RegradeOverride {...defaultProps} itemId={invalidItemId} />, {storeOptions}) const regradeButton = await screen.findByRole('button', {name: /regrade/i}) userEvent.click(regradeButton) expect(dispatchStub).not.toHaveBeenCalledWith({ payload: { options: {}, title: REGRADE_MODAL, }, type: OPEN_MODAL, }) }) it('shows errors with invalid input', async () => { render(<RegradeOverride {...defaultProps} itemId={invalidItemId} />, {storeOptions}) const regradeButton = screen.getByRole('button', {name: /regrade/i}) userEvent.click(regradeButton) expect(dispatchStub).toHaveBeenCalledWith({ payload: {key: '1'}, type: SHOW_ERROR, }) }) // TODO [QUIZ-16313]: Add support for MSW it.skip('sets the payload when regrading', async () => { render(<RegradeOverride {...defaultProps} />, { storeOptions: { state: {...defaultState, dispatchStub}, reviver: (key, value) => { if (key === 'regradingPayload') { return value.toJS() } return Iterable.isKeyed(value) ? value.toMap() : value.toList() }, }, }) const regradeButton = await screen.findByRole('button', {name: /regrade/i}) userEvent.click(regradeButton) expect(dispatchStub).toHaveBeenCalledWith( expect.objectContaining({ payload: expect.objectContaining({ itemId, quizEntryId, pointsPossible: newPointsPossible, scoringAlgorithm, scoringData: fromJS({ value: 'false', }), }), type: REGRADE_SET_PAYLOAD, }), ) }) // TODO [QUIZ-16334]: Simulate the regrading workflow including the confirmation modal in a larger test suite it('renders the correct answer after a regrade', () => { render( <RegradeOverride {...defaultProps} itemId={otherItemId} sessionItem={regradedSessionItem} />, {storeOptions}, ) expect(screen.getByRole('spinbutton', {name: /points/i})).toHaveValue('5') }) it('renders a spinner when the quizEntry ID is invalid', () => { const quizEntry = new QuizEntry({id: 0}) const quizEntryId = '0' const sessionItem = new SessionItem({ id: '333', quizEntryId, }) render( <RegradeOverride {...defaultProps} quizEntry={quizEntry} quizEntryId={quizEntryId} sessionItem={sessionItem} />, {storeOptions}, ) expect(screen.queryAllByText(/loading\.\.\./i).length).toBeGreaterThan(0) }) })