UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

325 lines (267 loc) • 9.54 kB
import {vi} from 'vitest' import React from 'react' import {ApplyLocale} from '@instructure/ui-i18n' import NumericTake from '../index' import {fireEvent, render, screen, waitFor} from '@testing-library/react' import runAxe from '@instructure/ui-axe-check' import userEvent from '@testing-library/user-event' describe('Numeric Take', () => { const props = { itemBody: '', userResponse: { value: '18', }, } numericTakeTests(NumericTake, props) }) export function numericTakeTests(Component, props) { let handleResponseUpdateStub beforeEach(() => { handleResponseUpdateStub = vi.fn() }) it('renders only one input', () => { render(<Component handleResponseUpdate={handleResponseUpdateStub} {...props} />) const inputs = screen.getAllByRole('spinbutton') expect(inputs.length).toBe(1) }) // To ensure that the component doesn't use a numeric keypad on mobile Chrome it('does not use a numeric input', () => { render(<Component handleResponseUpdate={handleResponseUpdateStub} {...props} />) const input = screen.getByRole('spinbutton') expect(input.getAttribute('inputmode')).not.toBe('numeric') }) it('passes value to NumberInput on mount', () => { render(<Component handleResponseUpdate={handleResponseUpdateStub} {...props} />) const input = screen.getByRole('spinbutton') expect(input.value).toBe(props.userResponse.value) }) it('does invoke response update callback on change', async () => { render(<Component handleResponseUpdate={handleResponseUpdateStub} {...props} />) const input = screen.getByRole('spinbutton') await userEvent.clear(input) await userEvent.type(input, '4') await userEvent.tab() expect(handleResponseUpdateStub).toHaveBeenCalled() }) it('invokes response update callback with value on change with valid input', async () => { render(<Component handleResponseUpdate={handleResponseUpdateStub} {...props} />) const input = screen.getByRole('spinbutton') fireEvent.change(input, {target: {value: '4'}}) fireEvent.blur(input) expect(handleResponseUpdateStub).toHaveBeenCalledWith('4', null, false) }) it('invokes response update callback with raw value on change with invalid input', async () => { render(<Component handleResponseUpdate={handleResponseUpdateStub} {...props} />) const input = screen.getByRole('spinbutton') fireEvent.change(input, {target: {value: 'lolwut'}}) fireEvent.blur(input) expect(handleResponseUpdateStub).toHaveBeenCalledWith('lolwut', null, false) }) it('does not invoke response update callback on blur with no change', async () => { render(<Component handleResponseUpdate={handleResponseUpdateStub} {...props} />) const input = screen.getByRole('spinbutton') await userEvent.click(input) await userEvent.tab() expect(handleResponseUpdateStub).not.toHaveBeenCalled() }) it('normalizes numeric responses', async () => { render( <ApplyLocale locale="fr"> <Component handleResponseUpdate={handleResponseUpdateStub} {...props} /> </ApplyLocale>, ) const input = screen.getByRole('spinbutton') fireEvent.change(input, {target: {value: '1,23'}}) fireEvent.blur(input) expect(handleResponseUpdateStub).toHaveBeenCalledWith('1.23', null, false) }) it('handles input in scientific notation', async () => { render( <ApplyLocale locale="fr"> <Component handleResponseUpdate={handleResponseUpdateStub} {...props} /> </ApplyLocale>, ) const input = screen.getByRole('spinbutton') fireEvent.change(input, {target: {value: '3.5*10^-2'}}) fireEvent.blur(input) expect(handleResponseUpdateStub).toHaveBeenCalledWith('3.5*10^-2', null, false) }) describe('a11y tests', () => { it('should meet a11y standards', async () => { const {container} = render( <Component handleResponseUpdate={handleResponseUpdateStub} {...props} />, ) expect( await runAxe(container, { ignores: [ 'label', 'region', 'scrollable-region-focusable', // we conditionally make rceRenderer focusable when we detect content overflow ], }), ).toBe(true) }) }) } // Non-shared tests describe('invalid input tests', () => { it('should alert if starting input is not a number', async () => { render( <NumericTake displayValidationWarning={true} handleResponseUpdate={Function.prototype} itemBody="" userResponse={{value: '53b'}} />, ) expect( screen.getByText(/answer must be a number. remove any symbols or units./i), ).toBeInTheDocument() }) it('should clear alert if user types in valid input', async () => { render( <NumericTake displayValidationWarning={true} handleResponseUpdate={Function.prototype} itemBody="" userResponse={{value: '53b'}} />, ) const input = screen.getByRole('spinbutton') await userEvent.clear(input) await userEvent.type(input, '123') await userEvent.tab() expect(screen.queryByText('Answer must be a number. Remove any symbols or units.')).toBeNull() }) it('should alert if user types in invalid input', async () => { render( <NumericTake displayValidationWarning={true} handleResponseUpdate={Function.prototype} itemBody="" userResponse={{value: ''}} />, ) const input = screen.getByRole('spinbutton') await userEvent.clear(input) await userEvent.type(input, '123b') await userEvent.tab() await waitFor(() => { expect( screen.getByText('Answer must be a number. Remove any symbols or units.'), ).toBeInTheDocument() }) }) it('should not alert if user types in valid input', async () => { render( <NumericTake displayValidationWarning={true} handleResponseUpdate={Function.prototype} itemBody="" userResponse={{value: ''}} />, ) const input = screen.getByRole('spinbutton') await userEvent.clear(input) await userEvent.type(input, '123') await userEvent.tab() expect(screen.queryByText('Answer must be a number. Remove any symbols or units.')).toBeNull() }) it('should not alert if user types in scientific notation', async () => { render( <NumericTake displayValidationWarning={true} handleResponseUpdate={Function.prototype} itemBody="" userResponse={{value: ''}} />, ) const input = screen.getByRole('spinbutton') await userEvent.clear(input) await userEvent.type(input, '1.23 * 10 ^ 4') await userEvent.tab() expect(screen.queryByText('Answer must be a number. Remove any symbols or units.')).toBeNull() }) }) describe('valid input tests', () => { let handleResponseUpdateStub beforeEach(() => { handleResponseUpdateStub = vi.fn() }) it('should convert english 1.9 to french 1,9', () => { render( <ApplyLocale locale="fr"> <NumericTake handleResponseUpdate={handleResponseUpdateStub} itemBody="" userResponse={{value: '1.9'}} /> </ApplyLocale>, ) const input = screen.getByRole('spinbutton') expect(input.value).toBe('1,9') }) it('should convert english 1000.9 to french 1 000,9', async () => { render( <ApplyLocale locale="fr"> <NumericTake handleResponseUpdate={handleResponseUpdateStub} itemBody="" userResponse={{value: '1000.9'}} /> </ApplyLocale>, ) const input = screen.getByRole('spinbutton') fireEvent.change(input, {target: {value: '1000.9'}}) fireEvent.blur(input) await waitFor(() => { expect(input.value).toBe('1\u202f000,9') }) }) }) describe('formatting alerts', () => { let handleResponseUpdateStub beforeEach(() => { handleResponseUpdateStub = vi.fn() }) it('should render an alert if the user input uses a different decimal separator than the one set in the locale', async () => { render( <ApplyLocale locale="fr"> <NumericTake displayValidationWarning={true} handleResponseUpdate={handleResponseUpdateStub} itemBody="" userResponse={{value: '1.9'}} /> </ApplyLocale>, ) const input = screen.getByRole('spinbutton') // The expected value is '1,9' as per the fr locale, so the alert should show up userEvent.clear(input) userEvent.type(input, '1.9') userEvent.tab() await waitFor(() => { expect( screen.getByText(/decimal separator auto-adjusted. verify your entry!/i), ).toBeInTheDocument() }) }) it('should not render an alert if only the thousand separator is different than the one set in the locale', async () => { render( <ApplyLocale locale="fr"> <NumericTake displayValidationWarning={true} handleResponseUpdate={handleResponseUpdateStub} itemBody="" userResponse={{value: '1000.9'}} /> </ApplyLocale>, ) const input = screen.getByRole('spinbutton') // The expected value is '1 000,9' as per the fr locale, so the alert should not show up userEvent.clear(input) userEvent.type(input, '1000,9') userEvent.tab() expect(screen.queryByText(/decimal separator auto-adjusted. verify your entry!/i)).toBeNull() }) })