diff --git a/src/tags/helpers/Tag.tsx b/src/tags/helpers/Tag.tsx index b61bc1b5..2e116a80 100644 --- a/src/tags/helpers/Tag.tsx +++ b/src/tags/helpers/Tag.tsx @@ -19,6 +19,6 @@ export const Tag: FC = ({ text, children, clearable, className = '', c onClick={onClick} > {children ?? text} - {clearable && ×} + {clearable && ×} ); diff --git a/src/utils/InfoTooltip.tsx b/src/utils/InfoTooltip.tsx index 2070c76e..15ecae62 100644 --- a/src/utils/InfoTooltip.tsx +++ b/src/utils/InfoTooltip.tsx @@ -5,7 +5,7 @@ import { UncontrolledTooltip } from 'reactstrap'; import { Placement } from '@popperjs/core'; import { mutableRefToElementRef } from './helpers/components'; -type InfoTooltipProps = PropsWithChildren<{ +export type InfoTooltipProps = PropsWithChildren<{ className?: string; placement: Placement; }>; diff --git a/src/utils/Result.tsx b/src/utils/Result.tsx index eacf40f0..e32872a8 100644 --- a/src/utils/Result.tsx +++ b/src/utils/Result.tsx @@ -15,6 +15,7 @@ export const Result: FC = ({ children, type, className, small = fal
{ + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + if (!result) { + throw new Error((`Could not convert color ${hex} to RGB`)); + } + + return { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + }; +}; + describe('', () => { const onClick = jest.fn(); const onClose = jest.fn(); const isColorLightForKey = jest.fn(() => false); const getColorForKey = jest.fn(() => MAIN_COLOR); const colorGenerator = Mock.of({ getColorForKey, isColorLightForKey }); - let wrapper: ShallowWrapper; - const createWrapper = (text: string, clearable?: boolean, children?: ReactNode) => { - wrapper = shallow( + const setUp = (text: string, clearable?: boolean, children?: ReactNode) => ({ + user: userEvent.setup(), + ...render( {children} , - ); - - return wrapper; - }; + ), + }); afterEach(jest.clearAllMocks); - afterEach(() => wrapper?.unmount()); it.each([ [true], @@ -31,9 +42,13 @@ describe('', () => { ])('includes an extra class when the color is light', (isLight) => { isColorLightForKey.mockReturnValue(isLight); - const wrapper = createWrapper('foo'); + const { container } = setUp('foo'); - expect((wrapper.prop('className') as string).includes('tag--light-bg')).toEqual(isLight); + if (isLight) { + expect(container.firstChild).toHaveClass('tag--light-bg'); + } else { + expect(container.firstChild).not.toHaveClass('tag--light-bg'); + } }); it.each([ @@ -45,21 +60,25 @@ describe('', () => { ])('includes generated color as backgroundColor', (generatedColor) => { getColorForKey.mockReturnValue(generatedColor); - const wrapper = createWrapper('foo'); + const { container } = setUp('foo'); + const { r, g, b } = hexToRgb(generatedColor); - expect((wrapper.prop('style') as any).backgroundColor).toEqual(generatedColor); + expect(container.firstChild).toHaveAttribute( + 'style', + expect.stringContaining(`background-color: rgb(${r}, ${g}, ${b})`), + ); }); - it('invokes expected callbacks when appropriate events are triggered', () => { - const wrapper = createWrapper('foo', true); + it('invokes expected callbacks when appropriate events are triggered', async () => { + const { container, user } = setUp('foo', true); - expect(onClick).not.toBeCalled(); - expect(onClose).not.toBeCalled(); + expect(onClick).not.toHaveBeenCalled(); + expect(onClose).not.toHaveBeenCalled(); - wrapper.simulate('click'); + container.firstElementChild && await user.click(container.firstElementChild); expect(onClick).toHaveBeenCalledTimes(1); - wrapper.find('.tag__close-selected-tag').simulate('click'); + await user.click(screen.getByLabelText('Close')); expect(onClose).toHaveBeenCalledTimes(1); }); @@ -68,18 +87,17 @@ describe('', () => { [false, 0, 'pointer'], [undefined, 0, 'pointer'], ])('includes a close component when the tag is clearable', (clearable, expectedCloseBtnAmount, expectedCursor) => { - const wrapper = createWrapper('foo', clearable); + const { container } = setUp('foo', clearable); - expect(wrapper.find('.tag__close-selected-tag')).toHaveLength(expectedCloseBtnAmount); - expect((wrapper.prop('style') as any).cursor).toEqual(expectedCursor); + expect(screen.queryAllByLabelText('Close')).toHaveLength(expectedCloseBtnAmount); + expect(container.firstChild).toHaveAttribute('style', expect.stringContaining(`cursor: ${expectedCursor}`)); }); it.each([ [undefined, 'foo'], ['bar', 'bar'], ])('falls back to text as children when no children are provided', (children, expectedChildren) => { - const wrapper = createWrapper('foo', false, children); - - expect(wrapper.html()).toContain(`>${expectedChildren}`); + const { container } = setUp('foo', false, children); + expect(container.firstChild).toHaveTextContent(expectedChildren); }); }); diff --git a/test/utils/InfoTooltip.test.tsx b/test/utils/InfoTooltip.test.tsx index efbe3af3..661e6bf0 100644 --- a/test/utils/InfoTooltip.test.tsx +++ b/test/utils/InfoTooltip.test.tsx @@ -1,30 +1,40 @@ -import { shallow } from 'enzyme'; -import { UncontrolledTooltip } from 'reactstrap'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { Placement } from '@popperjs/core'; -import { InfoTooltip } from '../../src/utils/InfoTooltip'; +import { InfoTooltip, InfoTooltipProps } from '../../src/utils/InfoTooltip'; describe('', () => { + const setUp = (props: Partial = {}) => ({ + user: userEvent.setup(), + ...render(), + }); + it.each([ [undefined], ['foo'], ['bar'], ])('renders expected className on span', (className) => { - const wrapper = shallow(); - const span = wrapper.find('span'); + const { container } = setUp({ className }); - expect(span.prop('className')).toEqual(className ?? ''); + if (className) { + expect(container.firstChild).toHaveClass(className); + } else { + expect(container.firstChild).toHaveAttribute('class', ''); + } }); it.each([ - [], - ['Foo'], - ['Hello'], - [['One', 'Two', ]], - ])('passes children down to the nested tooltip component', (children) => { - const wrapper = shallow({children}); - const tooltip = wrapper.find(UncontrolledTooltip); + [foo, 'foo'], + ['Foo', 'Foo'], + ['Hello', 'Hello'], + [['One', 'Two', ], 'OneTwo'], + ])('passes children down to the nested tooltip component', async (children, expectedContent) => { + const { container, user } = setUp({ children }); - expect(tooltip.prop('children')).toEqual(children); + container.firstElementChild && await user.hover(container.firstElementChild); + await waitFor(() => expect(screen.getByRole('tooltip')).toBeInTheDocument()); + screen.debug(screen.getByRole('tooltip')); + expect(screen.getByRole('tooltip')).toHaveTextContent(expectedContent); }); it.each([ @@ -32,10 +42,11 @@ describe('', () => { ['left' as Placement], ['top' as Placement], ['bottom' as Placement], - ])('places tooltip where requested', (placement) => { - const wrapper = shallow(); - const tooltip = wrapper.find(UncontrolledTooltip); + ])('places tooltip where requested', async (placement) => { + const { container, user } = setUp({ placement }); - expect(tooltip.prop('placement')).toEqual(placement); + container.firstElementChild && await user.hover(container.firstElementChild); + await waitFor(() => expect(screen.getByRole('tooltip')).toBeInTheDocument()); + expect(screen.getByRole('tooltip').parentNode).toHaveAttribute('data-popper-placement', placement); }); }); diff --git a/test/utils/Result.test.tsx b/test/utils/Result.test.tsx index b8a17cbb..0ba9b3f3 100644 --- a/test/utils/Result.test.tsx +++ b/test/utils/Result.test.tsx @@ -1,46 +1,32 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { render, screen } from '@testing-library/react'; import { Result, ResultProps, ResultType } from '../../src/utils/Result'; -import { SimpleCard } from '../../src/utils/SimpleCard'; describe('', () => { - let wrapper: ShallowWrapper; - const createWrapper = (props: ResultProps) => { - wrapper = shallow(); - - return wrapper; - }; - - afterEach(() => wrapper?.unmount()); + const setUp = (props: ResultProps) => render(); it.each([ ['success' as ResultType, 'bg-main text-white'], ['error' as ResultType, 'bg-danger text-white'], ['warning' as ResultType, 'bg-warning'], ])('renders expected classes based on type', (type, expectedClasses) => { - const wrapper = createWrapper({ type }); - const innerCard = wrapper.find(SimpleCard); - - expect(innerCard.prop('className')).toEqual(`text-center ${expectedClasses}`); + setUp({ type }); + expect(screen.getByRole('document')).toHaveClass(expectedClasses); }); it.each([ - [undefined], ['foo'], ['bar'], ])('renders provided classes in root element', (className) => { - const wrapper = createWrapper({ type: 'success', className }); - - expect(wrapper.prop('className')).toEqual(className); + const { container } = setUp({ type: 'success', className }); + expect(container.firstChild).toHaveClass(className); }); it.each([{ small: true }, { small: false }])('renders small results properly', ({ small }) => { - const wrapper = createWrapper({ type: 'success', small }); - const bigElement = wrapper.find('.col-md-10'); - const smallElement = wrapper.find('.col-12'); - const innerCard = wrapper.find(SimpleCard); + const { container } = setUp({ type: 'success', small }); + const bigElement = container.querySelectorAll('.col-md-10'); + const smallElement = container.querySelectorAll('.col-12'); expect(bigElement).toHaveLength(small ? 0 : 1); expect(smallElement).toHaveLength(small ? 1 : 0); - expect(innerCard.prop('bodyClassName')).toEqual(small ? 'p-2' : ''); }); }); diff --git a/test/utils/dates/DateIntervalDropdownItems.test.tsx b/test/utils/dates/DateIntervalDropdownItems.test.tsx index 8587e5e9..e0bd507a 100644 --- a/test/utils/dates/DateIntervalDropdownItems.test.tsx +++ b/test/utils/dates/DateIntervalDropdownItems.test.tsx @@ -1,41 +1,58 @@ -import { DropdownItem } from 'reactstrap'; -import { shallow, ShallowWrapper } from 'enzyme'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { DateIntervalDropdownItems } from '../../../src/utils/dates/DateIntervalDropdownItems'; -import { DATE_INTERVALS } from '../../../src/utils/dates/types'; +import { DATE_INTERVALS, DateInterval, rangeOrIntervalToString } from '../../../src/utils/dates/types'; +import { DropdownBtn } from '../../../src/utils/DropdownBtn'; describe('', () => { - let wrapper: ShallowWrapper; const onChange = jest.fn(); + const setUp = async () => { + const user = userEvent.setup(); + const renderResult = render( + + + , + ); - beforeEach(() => { - wrapper = shallow(); - }); + await user.click(screen.getByRole('button')); + await waitFor(() => expect(screen.getByRole('menu')).toBeInTheDocument()); + + return { user, ...renderResult }; + }; afterEach(jest.clearAllMocks); - afterEach(() => wrapper?.unmount()); - it('renders expected amount of items', () => { - const items = wrapper.find(DropdownItem); - const dividerItems = items.findWhere((item) => !!item.prop('divider')); + it('renders expected amount of items', async () => { + await setUp(); - expect(items).toHaveLength(DATE_INTERVALS.length + 2); - expect(dividerItems).toHaveLength(1); + expect(screen.getAllByRole('menuitem')).toHaveLength(DATE_INTERVALS.length + 1); + expect(screen.getByRole('menuitem', { name: 'Last 180 days' })).toHaveClass('active'); }); - it('sets expected item as active', () => { - const items = wrapper.find(DropdownItem).findWhere((item) => item.prop('active') !== undefined); - const EXPECTED_ACTIVE_INDEX = 6; + it('sets expected item as active', async () => { + await setUp(); + const EXPECTED_ACTIVE_INDEX = 5; - expect.assertions(DATE_INTERVALS.length + 1); - items.forEach((item, index) => expect(item.prop('active')).toEqual(index === EXPECTED_ACTIVE_INDEX)); + DATE_INTERVALS.forEach((interval, index) => { + const item = screen.getByRole('menuitem', { name: rangeOrIntervalToString(interval) }); + + if (index === EXPECTED_ACTIVE_INDEX) { + expect(item).toHaveClass('active'); + } else { + expect(item).not.toHaveClass('active'); + } + }); }); - it('triggers onChange callback when selecting an element', () => { - const items = wrapper.find(DropdownItem); + it.each([ + [3, 'last7Days' as DateInterval], + [7, 'last365Days' as DateInterval], + [2, 'yesterday' as DateInterval], + ])('triggers onChange callback when selecting an element', async (index, expectedInterval) => { + const { user } = await setUp(); - items.at(4).simulate('click'); - items.at(6).simulate('click'); - items.at(3).simulate('click'); - expect(onChange).toHaveBeenCalledTimes(3); + await user.click(screen.getAllByRole('menuitem')[index]); + + expect(onChange).toHaveBeenCalledWith(expectedInterval); }); }); diff --git a/test/utils/dates/DateIntervalSelector.test.tsx b/test/utils/dates/DateIntervalSelector.test.tsx index 2b73da55..0566247e 100644 --- a/test/utils/dates/DateIntervalSelector.test.tsx +++ b/test/utils/dates/DateIntervalSelector.test.tsx @@ -1,27 +1,26 @@ -import { shallow, ShallowWrapper } from 'enzyme'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { DateInterval, rangeOrIntervalToString } from '../../../src/utils/dates/types'; import { DateIntervalSelector } from '../../../src/utils/dates/DateIntervalSelector'; -import { DateIntervalDropdownItems } from '../../../src/utils/dates/DateIntervalDropdownItems'; -import { DropdownBtn } from '../../../src/utils/DropdownBtn'; describe('', () => { - let wrapper: ShallowWrapper; const activeInterval: DateInterval = 'last7Days'; const onChange = jest.fn(); - - beforeEach(() => { - wrapper = shallow(); + const setUp = () => ({ + user: userEvent.setup(), + ...render(), }); - afterEach(() => wrapper?.unmount()); - it('passes props down to nested DateIntervalDropdownItems', () => { - const items = wrapper.find(DateIntervalDropdownItems); - const dropdown = wrapper.find(DropdownBtn); + it('passes props down to nested DateIntervalDropdownItems', async () => { + const { user } = setUp(); + const btn = screen.getByRole('button'); - expect(dropdown.prop('text')).toEqual(rangeOrIntervalToString(activeInterval)); - expect(items).toHaveLength(1); - expect(items.prop('onChange')).toEqual(onChange); - expect(items.prop('active')).toEqual(activeInterval); - expect(items.prop('allText')).toEqual('All text'); + await user.click(btn); + await waitFor(() => expect(screen.getByRole('menu')).toBeInTheDocument()); + + const items = screen.getAllByRole('menuitem'); + + expect(btn).toHaveTextContent(rangeOrIntervalToString(activeInterval) ?? ''); + expect(items).toHaveLength(8); }); });