From 739d0b6820194f423ee0fcb91b1e2cc628a0a9fc Mon Sep 17 00:00:00 2001 From: "alex.saiannyi" Date: Wed, 1 Dec 2021 18:03:02 +0200 Subject: [PATCH] test(component): refactor counter component tests --- .../src/components/Counter/spec.tsx | 302 ++++++++++-------- 1 file changed, 160 insertions(+), 142 deletions(-) diff --git a/packages/big-design/src/components/Counter/spec.tsx b/packages/big-design/src/components/Counter/spec.tsx index 9d5cee16c..0867fe286 100644 --- a/packages/big-design/src/components/Counter/spec.tsx +++ b/packages/big-design/src/components/Counter/spec.tsx @@ -1,85 +1,95 @@ -import React, { createRef, Ref } from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { createRef, Ref, useState } from 'react'; import 'jest-styled-components'; -import { fireEvent, render } from '@test/utils'; +import { render } from '@test/utils'; import { FormControlDescription, FormControlError, FormControlLabel, FormGroup } from '../Form'; -import { Counter, CounterProps } from './index'; +import { Counter } from './index'; -interface MockCounterProps extends CounterProps { - dataTestId?: string; +interface MockProps { ref?: Ref; + id?: string | undefined; + label?: React.ReactChild; + labelId?: string; + dataTestId?: string; + description?: React.ReactChild; + value: number; + min?: number; + step?: number; + max?: number; + onCountChange?(count: number): void; + error?: React.ReactNode | React.ReactNode[]; } -const val = 5; - -const handleChange = jest.fn((num) => num); - -const requiredAttributes = { - value: val, - onCountChange: handleChange, -}; - -const counterMock = ({ +const CounterMock: React.FC = ({ ref, label = 'Label', labelId = '', - id = '', error = '', + id = '', description = '', - value, - onCountChange, - min = 0, - max = 10, + value = 5, dataTestId = '', -}: MockCounterProps) => ( - -); + min = 0, + max = 100, + onCountChange, +}) => { + const [counterValue, setCounterValue] = useState(value); + const handleChange = (value: number) => setCounterValue(value); + + return ( + + ); +}; test('forwards ref', () => { const ref = createRef(); - const { container } = render(counterMock({ ref, ...requiredAttributes })); - const counter = container.querySelector('input'); - expect(ref.current).toBe(counter); + render(); + + const input = screen.getByRole('textbox'); + + expect(ref.current).toBe(input); }); test('renders a counter as an input tag', () => { - const { container } = render(counterMock(requiredAttributes)); + render(); - expect(container.querySelector('input')).toBeInTheDocument(); + expect(screen.getByLabelText(/Label/i)).toBeInTheDocument(); }); test('renders a counter with matched label', () => { - const { queryByLabelText } = render(counterMock({ label: 'Test Label', ...requiredAttributes })); + render(); // This one checks for matching id and htmlFor - expect(queryByLabelText('Test Label')).toBeInTheDocument(); + expect(screen.getByLabelText(/test label/i)).toBeInTheDocument(); }); test('create unique ids if not provided', () => { - const { queryByTestId } = render( + render( <> - {counterMock({ dataTestId: 'item1', label: 'Test Label', ...requiredAttributes })} - {counterMock({ dataTestId: 'item2', label: 'Test Label', ...requiredAttributes })} + {} + {} , ); - const item1 = queryByTestId('item1') as HTMLInputElement; - const item2 = queryByTestId('item2') as HTMLInputElement; + const item1 = screen.getByTestId('item1'); + const item2 = screen.getByTestId('item2'); expect(item1).toBeDefined(); expect(item2).toBeDefined(); @@ -87,234 +97,239 @@ test('create unique ids if not provided', () => { }); test('respects provided id', () => { - const { container } = render(counterMock({ id: 'test', label: 'Test Label', ...requiredAttributes })); - const counter = container.querySelector('#test') as HTMLInputElement; + const providedIdValue = 'test'; + + render(); - expect(counter.id).toBe('test'); + const counterId = screen.getByLabelText(/test label/i).id; + + expect(counterId).toBe(providedIdValue); }); test('matches label htmlFor with id provided', () => { - const { container } = render(counterMock({ id: 'test', label: 'Test Label', ...requiredAttributes })); - const label = container.querySelector('label') as HTMLLabelElement; + render(); + + const labelForAttr = screen.getByText(/test label/i).htmlFor; - expect(label.htmlFor).toBe('test'); + expect(labelForAttr).toBe('test'); }); test('respects provided labelId', () => { - const { container } = render(counterMock({ label: 'Test Label', labelId: 'test', ...requiredAttributes })); - const label = container.querySelector('#test') as HTMLLabelElement; + render(); - expect(label.id).toBe('test'); + const labelId = screen.getByText(/test label/i).id; + + expect(labelId).toBe('test'); }); test('renders a description', () => { - const descriptionText = 'This is a description'; - const { queryByText } = render(counterMock({ description: descriptionText, ...requiredAttributes })); + const description = 'This is a description'; + + render(); - expect(queryByText(descriptionText)).toBeInTheDocument(); + expect(screen.getByText(description)).toBeInTheDocument(); }); test('renders an error', () => { - const errorText = 'This is an error'; - const { queryByText } = render({counterMock({ error: errorText, ...requiredAttributes })}); + const error = 'This is an error'; - expect(queryByText(errorText)).toBeInTheDocument(); + render({}); + + expect(screen.getByText(error)).toBeInTheDocument(); }); test('accepts a Label Component', () => { const CustomLabel = ( This is a custom Label - - has a url - + has a url ); - const { queryByTestId } = render(counterMock({ label: CustomLabel, ...requiredAttributes })); + render(); - expect(queryByTestId('test')).toBeInTheDocument(); + expect(screen.getByRole('link')).toBeInTheDocument(); }); test('does not accept non-Label Components', () => { const NotALabel = (
This is a not custom Label Component - - has a url - + has a url
); - const { queryByTestId } = render(counterMock({ label: NotALabel, ...requiredAttributes })); + render(); - expect(queryByTestId('test')).not.toBeInTheDocument(); + expect(screen.queryByRole('link')).not.toBeInTheDocument(); }); test('accepts a Description Component', () => { const CustomDescription = ( This is a custom Description - - has a url - + has a url ); - const { queryByTestId } = render(counterMock({ description: CustomDescription, ...requiredAttributes })); + render(); - expect(queryByTestId('test')).toBeInTheDocument(); + expect(screen.getByRole('link')).toBeInTheDocument(); }); test('does not accept non-Description Components', () => { const NotADescription = (
This is a not custom description - - has a url - + has a url
); - const { queryByTestId } = render(counterMock({ description: NotADescription, ...requiredAttributes })); + render(); - expect(queryByTestId('test')).not.toBeInTheDocument(); + expect(screen.queryByRole('link')).not.toBeInTheDocument(); }); test('accepts an Error Component', () => { const CustomError = ( This is a custom Error Component - - has a url - + has a url ); - const { queryByTestId } = render({counterMock({ error: CustomError, ...requiredAttributes })}); + render({}); - expect(queryByTestId('test')).toBeInTheDocument(); + expect(screen.getByRole('link')).toBeInTheDocument(); }); test('does not accept non-Error Components', () => { const NotAnError = (
This is a not a custom error component - - has a url - + has a url
); - const { queryByTestId } = render({counterMock({ error: NotAnError, ...requiredAttributes })}); + render({}); - expect(queryByTestId('test')).not.toBeInTheDocument(); + expect(screen.queryByRole('link')).not.toBeInTheDocument(); }); test('renders both the add and subtract icons', () => { - const { getAllByRole } = render(counterMock(requiredAttributes)); + render(); - const buttons = getAllByRole('button'); + const buttons = screen.getAllByRole('button'); - expect(buttons.length).toBe(2); + expect(buttons).toHaveLength(2); }); test('buttons are disabled when value hits max or min', () => { - const { getAllByRole, rerender } = render(counterMock({ ...requiredAttributes, value: 0 })); + const { rerender } = render(); - const buttons = getAllByRole('button'); + const buttons = screen.getAllByRole('button'); - expect(buttons[0]).toHaveProperty('disabled', true); - expect(buttons[1]).toHaveProperty('disabled', false); + expect(buttons[0]).toBeDisabled; + expect(buttons[1]).toBeEnabled; - rerender(counterMock({ ...requiredAttributes, value: 10 })); + rerender(); - expect(buttons[0]).toHaveProperty('disabled', false); - expect(buttons[1]).toHaveProperty('disabled', true); + expect(buttons[0]).toBeEnabled; + expect(buttons[1]).toBeDisabled; }); test('value prop only accepts whole numbers', () => { - const { container } = render(counterMock({ max: 20, ...requiredAttributes })); + render(); + + const input = screen.getByRole('textbox'); + const fractionalValue = '1.7'; - const input = container.getElementsByTagName('input'); - fireEvent.change(input[0], { target: { value: 1.5 } }); + userEvent.clear(input); + userEvent.type(input, fractionalValue); - expect(handleChange).toHaveBeenCalledWith(2); + expect(input).toHaveValue('17'); }); test('value increases when increase or decrease icons are clicked', () => { - const { getByDisplayValue, container } = render(counterMock(requiredAttributes)); + const handleChange = jest.fn((num: number) => num); + render(); - const counter = getByDisplayValue('5') as HTMLInputElement; - - const icons = container.getElementsByTagName('svg'); + const counterValue = screen.getByRole('textbox').value; + const icons = Array.from(screen.getAllByTitle(/[a-z]{2}crease count/i)).map( + (title: HTMLElement): HTMLElement | null => title.parentElement, + ); - expect(counter.value).toEqual('5'); - fireEvent.click(icons[1]); + expect(counterValue).toEqual('5'); + icons[1] && userEvent.click(icons[1]); expect(handleChange).toHaveBeenCalledWith(6); - fireEvent.click(icons[0]); + icons[0] && userEvent.click(icons[0]); expect(handleChange).toHaveBeenCalledWith(4); }); test('value increases and decreases with arrow keypresses', () => { - const { getByDisplayValue } = render(counterMock(requiredAttributes)); + const handleChange = jest.fn((num: number) => num); + render(); - const counter = getByDisplayValue('5') as HTMLInputElement; + const counter = screen.getByRole('textbox'); counter.focus(); expect(counter.value).toEqual('5'); - fireEvent.keyDown(counter, { key: 'ArrowUp', code: 'ArrowUp' }); + userEvent.type(counter, '{arrowup}'); expect(handleChange).toHaveBeenCalledWith(6); - fireEvent.keyDown(counter, { key: 'ArrowDown', code: 'ArrowDown' }); + userEvent.type(counter, '{arrowdown}'); expect(handleChange).toHaveBeenCalledWith(4); }); test('value is set to 0 when pressing Escape', () => { - const { getByDisplayValue } = render(counterMock(requiredAttributes)); + const handleChange = jest.fn((num: number) => num); + render(); - const counter = getByDisplayValue('5'); + const counter = screen.getByRole('textbox'); - expect(counter.getAttribute('value')).toEqual('5'); - fireEvent.keyDown(counter, { key: 'Escape', code: 'Escape' }); + expect(counter).toHaveValue('5'); + userEvent.type(counter, '{esc}'); expect(handleChange).toHaveBeenCalledWith(0); }); test('value does not change when pressing Enter', () => { - const { getByDisplayValue } = render(counterMock(requiredAttributes)); + const handleChange = jest.fn((num: number) => num); + render(); - const counter = getByDisplayValue('5'); + const counter = screen.getByRole('textbox'); - expect(counter.getAttribute('value')).toEqual('5'); - fireEvent.keyDown(counter, { key: 'Enter', code: 'Enter' }); + expect(counter).toHaveValue('5'); + userEvent.type(counter, '{enter}'); expect(handleChange).not.toHaveBeenCalled(); }); test('provided onCountChange function is called on value change', () => { - const { getByTitle } = render(counterMock(requiredAttributes)); + const handleChange = jest.fn((num: number) => num); + render(); - const increase = getByTitle('Increase count'); + const increase = screen.getByTitle('Increase count'); - fireEvent.click(increase); + userEvent.click(increase); expect(handleChange).toHaveBeenCalledWith(6); }); test('error shows with valid string', () => { const error = 'Error'; - const { container, rerender } = render({counterMock({ error: '', ...requiredAttributes })}); + const { rerender } = render({}); - expect(container.querySelector('[class*="StyledError"]')).not.toBeInTheDocument(); + expect(screen.queryByText('Error')).not.toBeInTheDocument(); - rerender({counterMock({ error, ...requiredAttributes })}); + rerender({}); - expect(container.querySelector('[class*="StyledError"]')).toBeInTheDocument(); + expect(screen.getByText(error).parentElement).toBeInTheDocument(); }); test('error shows when an array of strings', () => { const errors = ['Error 0', 'Error 1']; - const { getByText } = render({counterMock({ error: errors, ...requiredAttributes })}); - errors.forEach((error) => expect(getByText(error)).toBeInTheDocument()); + render({}); + + errors.forEach((error) => expect(screen.getByText(error)).toBeInTheDocument()); }); test('error shows when an array of Errors', () => { @@ -324,17 +339,19 @@ test('error shows when an array of Errors', () => { Error )); - const { getByTestId } = render({counterMock({ error: errors, ...requiredAttributes })}); - testIds.forEach((id) => expect(getByTestId(id)).toBeInTheDocument()); + render({}); + + testIds.forEach((id) => expect(screen.getByTestId(id)).toBeInTheDocument()); }); describe('error does not show when invalid type', () => { test('single element', () => { const error =
Error
; - const { queryByTestId } = render({counterMock({ error, ...requiredAttributes })}); - expect(queryByTestId('err')).not.toBeInTheDocument(); + render({}); + + expect(screen.queryByTestId('err')).not.toBeInTheDocument(); }); test('array of elements', () => { @@ -346,15 +363,16 @@ describe('error does not show when invalid type', () => { , ]; - const { queryByTestId } = render({counterMock({ error: errors, ...requiredAttributes })}); + render({}); - expect(queryByTestId('err')).not.toBeInTheDocument(); + expect(screen.queryByTestId('err')).not.toBeInTheDocument(); }); }); test('appends (optional) text to label if input is not required', () => { - const { container } = render(counterMock({ label: 'Test Label', ...requiredAttributes })); - const label = container.querySelector('label'); + render(); + + const label = screen.getByText(/test label/i); expect(label).toHaveStyleRule('content', "' (optional)'", { modifier: '::after' }); });