mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-10 18:27:25 +03:00
Merge pull request #681 from acelaya-forks/feature/testing-lib
Feature/testing lib
This commit is contained in:
commit
fd7aa570ed
8 changed files with 140 additions and 108 deletions
|
@ -19,6 +19,6 @@ export const Tag: FC<TagProps> = ({ text, children, clearable, className = '', c
|
|||
onClick={onClick}
|
||||
>
|
||||
{children ?? text}
|
||||
{clearable && <span className="close tag__close-selected-tag" onClick={onClose}>×</span>}
|
||||
{clearable && <span aria-label="Close" className="close tag__close-selected-tag" onClick={onClose}>×</span>}
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}>;
|
||||
|
|
|
@ -15,6 +15,7 @@ export const Result: FC<ResultProps> = ({ children, type, className, small = fal
|
|||
<Row className={className}>
|
||||
<div className={classNames({ 'col-md-10 offset-md-1': !small, 'col-12': small })}>
|
||||
<SimpleCard
|
||||
role="document"
|
||||
className={classNames('text-center', {
|
||||
'bg-main': type === 'success',
|
||||
'bg-danger': type === 'error',
|
||||
|
|
|
@ -1,29 +1,40 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { ReactNode } from 'react';
|
||||
import { ColorGenerator } from '../../../src/utils/services/ColorGenerator';
|
||||
import { MAIN_COLOR } from '../../../src/utils/theme';
|
||||
import { Tag } from '../../../src/tags/helpers/Tag';
|
||||
|
||||
const hexToRgb = (hex: string) => {
|
||||
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('<Tag />', () => {
|
||||
const onClick = jest.fn();
|
||||
const onClose = jest.fn();
|
||||
const isColorLightForKey = jest.fn(() => false);
|
||||
const getColorForKey = jest.fn(() => MAIN_COLOR);
|
||||
const colorGenerator = Mock.of<ColorGenerator>({ 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(
|
||||
<Tag text={text} clearable={clearable} colorGenerator={colorGenerator} onClick={onClick} onClose={onClose}>
|
||||
{children}
|
||||
</Tag>,
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
),
|
||||
});
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it.each([
|
||||
[true],
|
||||
|
@ -31,9 +42,13 @@ describe('<Tag />', () => {
|
|||
])('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('<Tag />', () => {
|
|||
])('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('<Tag />', () => {
|
|||
[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}</span>`);
|
||||
const { container } = setUp('foo', false, children);
|
||||
expect(container.firstChild).toHaveTextContent(expectedChildren);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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('<InfoTooltip />', () => {
|
||||
const setUp = (props: Partial<InfoTooltipProps> = {}) => ({
|
||||
user: userEvent.setup(),
|
||||
...render(<InfoTooltip placement="right" {...props} />),
|
||||
});
|
||||
|
||||
it.each([
|
||||
[undefined],
|
||||
['foo'],
|
||||
['bar'],
|
||||
])('renders expected className on span', (className) => {
|
||||
const wrapper = shallow(<InfoTooltip placement="right" className={className} />);
|
||||
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([
|
||||
[<span key={1} />],
|
||||
['Foo'],
|
||||
['Hello'],
|
||||
[['One', 'Two', <span key={3} />]],
|
||||
])('passes children down to the nested tooltip component', (children) => {
|
||||
const wrapper = shallow(<InfoTooltip placement="right">{children}</InfoTooltip>);
|
||||
const tooltip = wrapper.find(UncontrolledTooltip);
|
||||
[<span key={1}>foo</span>, 'foo'],
|
||||
['Foo', 'Foo'],
|
||||
['Hello', 'Hello'],
|
||||
[['One', 'Two', <span key={3} />], '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('<InfoTooltip />', () => {
|
|||
['left' as Placement],
|
||||
['top' as Placement],
|
||||
['bottom' as Placement],
|
||||
])('places tooltip where requested', (placement) => {
|
||||
const wrapper = shallow(<InfoTooltip placement={placement} />);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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('<Result />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (props: ResultProps) => {
|
||||
wrapper = shallow(<Result {...props} />);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
const setUp = (props: ResultProps) => render(<Result {...props} />);
|
||||
|
||||
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' : '');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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('<DateIntervalDropdownItems />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const onChange = jest.fn();
|
||||
const setUp = async () => {
|
||||
const user = userEvent.setup();
|
||||
const renderResult = render(
|
||||
<DropdownBtn text="text">
|
||||
<DateIntervalDropdownItems allText="All" active="last180Days" onChange={onChange} />
|
||||
</DropdownBtn>,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<DateIntervalDropdownItems allText="All" active="last180Days" onChange={onChange} />);
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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('<DateIntervalSelector />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const activeInterval: DateInterval = 'last7Days';
|
||||
const onChange = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<DateIntervalSelector allText="All text" active={activeInterval} onChange={onChange} />);
|
||||
const setUp = () => ({
|
||||
user: userEvent.setup(),
|
||||
...render(<DateIntervalSelector allText="All text" active={activeInterval} onChange={onChange} />),
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue