UNPKG

@instructure/quiz-grading

Version:

The Quiz React SDK by Instructure Inc.

207 lines (181 loc) • 8.02 kB
import React from 'react' import {List} from 'immutable' import {render, screen} from '../../../../../tests/utils/rtlRenderOverride' import userEvent from '@testing-library/user-event' import {describe, expect, it, vi, beforeEach} from 'vitest' import InteractionType from '@instructure/quiz-interactions/records/InteractionType' import {Item} from '@instructure/quiz-core/common/records/Item' import {SessionItem} from '@instructure/quiz-core/common/records/SessionItem' import {GradingSidebar as Sidebar} from '../presenter' import {FORMULA_SLUG, RICH_FILL_BLANK_SLUG} from '@instructure/quiz-common/constants' import runAxeCheck from '@instructure/ui-axe-check' describe('Grading Sidebar Presenter', () => { const intTypeA = new InteractionType({name: 'Fill in the Blank', slug: RICH_FILL_BLANK_SLUG}) const intTypeB = new InteractionType({name: 'Multiple Choice'}) const itemA = new Item({ id: '5', position: 4, itemBody: '<div>first</div>&amp;<div>second <span id="blank_abcd-1234"></span></div>', }) const itemB = new Item({id: '6', position: 8}) const itemC = new Item({ id: '7', position: 5, itemBody: 'what is 5 + `x`?', interactionData: {variables: [{name: 'x', value: 7}]}, }) const sessionItemA = new SessionItem({id: '5', position: 1, questionNumber: 1, pointsPossible: 5}) const sessionItemB = new SessionItem({id: '6', position: 2, questionNumber: 2, pointsPossible: 5}) const sessionItemC = new SessionItem({id: '7', position: 4, questionNumber: 3, pointsPossible: 5}) beforeEach(() => { vi.spyOn(itemA, 'getInteractionType').mockReturnValue(intTypeA) vi.spyOn(itemB, 'getInteractionType').mockReturnValue(intTypeB) vi.spyOn(itemC, 'getInteractionType').mockReturnValue( new InteractionType({name: 'Formula', slug: FORMULA_SLUG}), ) vi.spyOn(sessionItemA, 'getItem').mockReturnValue(itemA) vi.spyOn(sessionItemB, 'getItem').mockReturnValue(itemB) vi.spyOn(sessionItemC, 'getItem').mockReturnValue(itemC) }) const sessionItems = List([sessionItemA, sessionItemB, sessionItemC]) const noop = () => {} const defaultProps = { quizTitle: 'Super fun quiz', scrollToItem: noop, sidebarOpen: true, screenreaderNotification: noop, sessionItems, sessionNotLoaded: false, toggleSidebar: () => {}, } describe('#renderSidebarItems', () => { it('renders the correct number', () => { render(<Sidebar {...defaultProps} />) const sidebarItems = screen.getAllByRole('link', { name: /Navigate to question at position/i, }) expect(sidebarItems).toHaveLength(3) }) it('should extract text content of item body HTML', () => { render(<Sidebar {...defaultProps} />) expect(screen.getByText(/first&second _____/i)).toBeInTheDocument() }) it('should replace the variables in formula questions', () => { render(<Sidebar {...defaultProps} />) expect(screen.getByText(/what is 5 \+ 7\?/i)).toBeInTheDocument() }) describe('textblock handling', () => { it('should display textblocks but not count them in question numbering', () => { // Create textblock items (questionNumber: 0) const textblockIntType = new InteractionType({ name: 'No Interaction', slug: 'no-interaction', }) const textblockItem1 = new Item({id: '100', position: 1}) const textblockItem2 = new Item({id: '101', position: 6}) const questionIntType = new InteractionType({name: 'Multiple Choice', slug: 'choice'}) const questionItem1 = new Item({id: '102', position: 2}) const questionItem2 = new Item({id: '103', position: 3}) const questionItem3 = new Item({id: '104', position: 7}) // Mock interaction types vi.spyOn(textblockItem1, 'getInteractionType').mockReturnValue(textblockIntType) vi.spyOn(textblockItem2, 'getInteractionType').mockReturnValue(textblockIntType) vi.spyOn(questionItem1, 'getInteractionType').mockReturnValue(questionIntType) vi.spyOn(questionItem2, 'getInteractionType').mockReturnValue(questionIntType) vi.spyOn(questionItem3, 'getInteractionType').mockReturnValue(questionIntType) // Create session items with textblocks at positions 1 and 6 const textblockSession1 = new SessionItem({ id: '100', position: 1, questionNumber: 0, pointsPossible: 0, }) const questionSession1 = new SessionItem({ id: '102', position: 2, questionNumber: 1, pointsPossible: 1, }) const questionSession2 = new SessionItem({ id: '103', position: 3, questionNumber: 2, pointsPossible: 1, }) const textblockSession2 = new SessionItem({ id: '101', position: 6, questionNumber: 0, pointsPossible: 0, }) const questionSession3 = new SessionItem({ id: '104', position: 7, questionNumber: 3, pointsPossible: 1, }) // Mock getItem for all session items vi.spyOn(textblockSession1, 'getItem').mockReturnValue(textblockItem1) vi.spyOn(textblockSession2, 'getItem').mockReturnValue(textblockItem2) vi.spyOn(questionSession1, 'getItem').mockReturnValue(questionItem1) vi.spyOn(questionSession2, 'getItem').mockReturnValue(questionItem2) vi.spyOn(questionSession3, 'getItem').mockReturnValue(questionItem3) const sessionItemsWithTextblocks = List([ textblockSession1, questionSession1, questionSession2, textblockSession2, questionSession3, ]) render(<Sidebar {...defaultProps} sessionItems={sessionItemsWithTextblocks} />) // All items (including textblocks) should be rendered const allItems = screen.getAllByRole('link', { name: /Navigate to question at position/i, }) expect(allItems).toHaveLength(5) // 2 textblocks + 3 questions // Questions should be numbered 1, 2, 3 (not 1, 2, 4 or 2, 3, 4) expect(screen.getByRole('link', {name: /position 1/i})).toBeInTheDocument() expect(screen.getByRole('link', {name: /position 2/i})).toBeInTheDocument() expect(screen.getByRole('link', {name: /position 3/i})).toBeInTheDocument() // Textblocks should have position 0 (not numbered) expect(screen.getAllByRole('link', {name: /position 0/i})).toHaveLength(2) }) }) }) describe('scrolling and changing items', () => { const scrollToItemMock = vi.fn() const screenreaderNotificationMock = vi.fn() it('binds scrollToItem to the itemId', () => { render( <Sidebar {...defaultProps} scrollToItem={scrollToItemMock} screenreaderNotification={screenreaderNotificationMock} />, ) const firstSidebarItem = screen.getAllByRole('link', { name: /Navigate to question at position/i, })[0] userEvent.click(firstSidebarItem) expect(scrollToItemMock).toHaveBeenCalledWith('5') }) }) describe('#render', () => { it('renders the sidebar navigation', () => { render(<Sidebar {...defaultProps} />) expect(screen.queryByRole('navigation')).toBeInTheDocument() }) it('renders nothing if sessionNotLoaded is true', () => { render(<Sidebar {...defaultProps} sessionNotLoaded={true} />) expect(screen.queryByRole('navigation')).not.toBeInTheDocument() }) it('passes the role prop to the sidebar component', () => { render(<Sidebar {...defaultProps} role={'complementary'} />) expect(screen.queryByRole('complementary')).toBeInTheDocument() }) }) it('should meet a11y standards', async () => { render(<Sidebar {...defaultProps} />) expect(await runAxeCheck(document.body, {ignores: ['aria-tooltip-name']})).toBe(true) }) })