UNPKG

@instructure/quiz-grading

Version:

The Quiz React SDK by Instructure Inc.

266 lines (228 loc) 9.04 kB
import React from 'react' import {Map} from 'immutable' import {beforeEach, describe, expect, it, vi} from 'vitest' import {render, screen, waitFor} from '../../../../../../tests/utils/rtlRenderOverride' import userEvent from '@testing-library/user-event' import {Item as ItemRecord} from '@instructure/quiz-core/common/records/Item' import {SessionItem as SessionItemRecord} from '@instructure/quiz-core/common/records/SessionItem' import {SessionItemResult as SessionItemResultRecord} from '@instructure/quiz-core/common/records/SessionItemResult' import {SessionItemResultWrapper} from '../presenter' describe('SessionItemResultWrapper Presenter', () => { const id = '1' const item = new ItemRecord({id}) const sessionItem = new SessionItemRecord({id}) const sessionItemResult = new SessionItemResultRecord({id, score: 0}) let setActiveItemStub = vi.fn() let switchOnEditingStub = vi.fn() let switchOffEditingStub = vi.fn() let handleChangeFeedbackStub = vi.fn() const defaultProps = { activeQuizSessionId: '1', appContainer: '.root', graderFeedback: Map(), handleChangeFeedback: handleChangeFeedbackStub, handleChangeGradeStatus: Function.prototype, handleChangePoints: Function.prototype, isEditing: false, isRegrading: false, isSurvey: false, item, itemResultsModifications: Map([['scoredData', ['gradeStatus', 'graded']]]), regradingKey: 'someKey', renderUpdateButton: Function.prototype, sessionItem, sessionItemResult, setActiveItem: setActiveItemStub, shouldRenderTopBorder: false, switchOnEditing: switchOnEditingStub, switchOffEditing: switchOffEditingStub, toggleRegradingEditMode: Function.prototype, quizSessionId: '1', } beforeEach(() => { setActiveItemStub = vi.fn() switchOnEditingStub = vi.fn() switchOffEditingStub = vi.fn() handleChangeFeedbackStub = vi.fn() }) describe('focus tests', () => { it('focuses on the feedback button on closeFeedbackEdit', () => { render(<SessionItemResultWrapper {...defaultProps} isEditing />) const feedbackButton = screen.getByRole('button', {name: /edit grader level feedback/i}) const cancelButton = screen.getByRole('button', {name: /cancel/i}) userEvent.click(cancelButton) expect(feedbackButton).toHaveFocus() }) describe('gradeByQuestion enabled', () => { it('sets activeItem when focused', () => { const setActiveItemStub = vi.fn() const {container} = render( <SessionItemResultWrapper {...defaultProps} gradeByQuestionEnabled={true} setActiveItem={setActiveItemStub} />, ) const wrapperElement = container.querySelector('[data-automation="sdk-position-box-text"]') userEvent.click(wrapperElement) expect(setActiveItemStub).toHaveBeenCalled() }) }) describe('gradeByQuestion disabled', () => { it('does not set activeItem when focused', () => { const setActiveItemStub = vi.fn() const {container} = render( <SessionItemResultWrapper {...defaultProps} setActiveItem={setActiveItemStub} />, ) const wrapperElement = container.querySelector( '[data-automation="sdk-grading-result-wrapper"]', ) userEvent.click(wrapperElement) expect(setActiveItemStub).not.toHaveBeenCalled() }) }) }) describe('feedback tests', () => { it('calls startFeedbackEdit correctly', () => { const switchOnEditingStub = vi.fn() render(<SessionItemResultWrapper {...defaultProps} switchOnEditing={switchOnEditingStub} />) const editButton = screen.getByRole('button', { name: /edit grader level feedback/i, }) userEvent.click(editButton) expect(switchOnEditingStub).toHaveBeenCalled() }) it('returns startFeedbackEdit early if already editing', () => { const switchOnEditingStub = vi.fn() render( <SessionItemResultWrapper {...defaultProps} isEditing switchOnEditing={switchOnEditingStub} />, ) const editButton = screen.getByRole('button', { name: /edit grader level feedback/i, }) userEvent.click(editButton) expect(switchOnEditingStub).not.toHaveBeenCalled() }) it('calls closeFeedbackEdit correctly', () => { const switchOffEditingStub = vi.fn() render( <SessionItemResultWrapper {...defaultProps} isEditing switchOffEditing={switchOffEditingStub} />, ) const cancelButton = screen.getByRole('button', { name: /Cancel/i, }) userEvent.click(cancelButton) expect(switchOffEditingStub).toHaveBeenCalled() }) it('calls submitFeedbackEdit correctly', () => { const switchOffEditingStub = vi.fn() const handleChangeFeedbackStub = vi.fn() render( <SessionItemResultWrapper {...defaultProps} isEditing handleChangeFeedback={handleChangeFeedbackStub} switchOffEditing={switchOffEditingStub} />, ) const doneButton = screen.getByRole('button', { name: /Done/i, }) userEvent.click(doneButton) expect(handleChangeFeedbackStub).toHaveBeenCalled() expect(switchOffEditingStub).toHaveBeenCalled() }) }) describe('handler tests', () => { it('calls toggleRegradingEditMode with regradingKey on handleRegradingEditModeToggle', () => { const toggleRegradingEditMode = vi.fn() render( <SessionItemResultWrapper {...defaultProps} toggleRegradingEditMode={toggleRegradingEditMode} regradingKey="myKey" />, {storeOptions: {state: {features: ['regrading_from_speedgrader']}}}, ) const regradeButton = document.querySelector('[data-automation="sdk-regrade-request-button"]') userEvent.click(regradeButton) expect(toggleRegradingEditMode).toHaveBeenCalledWith('myKey') }) it('focuses the regrade button after canceling regrading', async () => { // Render with isRegrading=false so GradingBar mounts and gradingBar ref is set. // Then call handleRegradingOnCancel directly via class ref — no need to render // RegradeOverride or its complex store dependencies. const ref = React.createRef() render( <SessionItemResultWrapper ref={ref} {...defaultProps} isRegrading={false} regradingFromSpeedGrader={true} />, {storeOptions: {state: {features: ['regrading_from_speedgrader']}}}, ) ref.current.handleRegradingOnCancel(id) await waitFor(() => { expect( document.querySelector('[data-automation="sdk-regrade-request-button"]'), ).toHaveFocus() }) }) }) describe('regrading_from_speedgrader tests', () => { it('renders RegradeOverride (not the grading result wrapper) when isRegrading and regradingFromSpeedGrader are true', () => { // When isRegrading && regradingFromSpeedGrader, SessionItemResultWrapper.render() returns // renderRegradeOverride() instead of renderResult(). With a minimal store the connected // RegradeOverride renders a Spinner (quizEntry.id === '0'). Assert the grading result // wrapper is absent to confirm the regrading branch was taken. // // Store must include regrading.scoreReconciliation because RegradeOverride renders a // connected RegradingModal which accesses that value unconditionally in renderModalBody(). const {container} = render( <SessionItemResultWrapper {...defaultProps} isRegrading={true} regradingFromSpeedGrader={true} />, { storeOptions: { state: { features: ['regrading_from_speedgrader'], regrading: {scoreReconciliation: 'new_score'}, }, }, }, ) expect(container.querySelector('[data-automation="sdk-grading-result-wrapper"]')).toBeNull() }) it('updateFeedbackEdit updates the grader feedback content in state', () => { const handleChangeFeedback = vi.fn() render( <SessionItemResultWrapper {...defaultProps} isEditing={true} handleChangeFeedback={handleChangeFeedback} />, ) // The RichContentInput onChange triggers updateFeedbackEdit which updates graderFeedback state. // Submitting the feedback form calls handleChangeFeedback with the updated state. const doneButton = screen.getByRole('button', {name: /Done/i}) userEvent.click(doneButton) // handleChangeFeedback is called with the current graderFeedback state expect(handleChangeFeedback).toHaveBeenCalledWith( sessionItem.id, item.id, expect.objectContaining({graderFeedback: expect.any(Object)}), ) }) }) })