mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-12-11 02:09:53 -06:00
Merge pull request #662 from acelaya-forks/feature/tests-all-over
Feature/tests all over
This commit is contained in:
commit
53e15b041d
@ -8,6 +8,7 @@ import {
|
|||||||
faCircleNotch as loadingStatusIcon,
|
faCircleNotch as loadingStatusIcon,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { MediaMatcher } from '../../utils/types';
|
import { MediaMatcher } from '../../utils/types';
|
||||||
|
import { mutableRefToElementRef } from '../../utils/helpers/components';
|
||||||
import { DomainStatus } from '../data';
|
import { DomainStatus } from '../data';
|
||||||
|
|
||||||
interface DomainStatusIconProps {
|
interface DomainStatusIconProps {
|
||||||
@ -34,11 +35,7 @@ export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span
|
<span ref={mutableRefToElementRef(ref)}>
|
||||||
ref={(el: HTMLSpanElement) => {
|
|
||||||
ref.current = el;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{status === 'valid'
|
{status === 'valid'
|
||||||
? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" />
|
? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" />
|
||||||
: <FontAwesomeIcon fixedWidth icon={invalidIcon} className="text-danger" />}
|
: <FontAwesomeIcon fixedWidth icon={invalidIcon} className="text-danger" />}
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { useRef, RefObject, ChangeEvent, MutableRefObject, useState, useEffect, FC, PropsWithChildren } from 'react';
|
import { useRef, ChangeEvent, useState, useEffect, FC, PropsWithChildren } from 'react';
|
||||||
import { Button, UncontrolledTooltip } from 'reactstrap';
|
import { Button, UncontrolledTooltip } from 'reactstrap';
|
||||||
import { complement, pipe } from 'ramda';
|
import { complement, pipe } from 'ramda';
|
||||||
import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { useToggle } from '../../utils/helpers/hooks';
|
import { useToggle } from '../../utils/helpers/hooks';
|
||||||
|
import { mutableRefToElementRef } from '../../utils/helpers/components';
|
||||||
import { ServersImporter } from '../services/ServersImporter';
|
import { ServersImporter } from '../services/ServersImporter';
|
||||||
import { ServerData, ServersMap } from '../data';
|
import { ServerData, ServersMap } from '../data';
|
||||||
import { DuplicatedServersModal } from './DuplicatedServersModal';
|
import { DuplicatedServersModal } from './DuplicatedServersModal';
|
||||||
import './ImportServersBtn.scss';
|
import './ImportServersBtn.scss';
|
||||||
|
|
||||||
type Ref<T> = RefObject<T> | MutableRefObject<T>;
|
|
||||||
|
|
||||||
export type ImportServersBtnProps = PropsWithChildren<{
|
export type ImportServersBtnProps = PropsWithChildren<{
|
||||||
onImport?: () => void;
|
onImport?: () => void;
|
||||||
onImportError?: (error: Error) => void;
|
onImportError?: (error: Error) => void;
|
||||||
@ -21,7 +20,6 @@ export type ImportServersBtnProps = PropsWithChildren<{
|
|||||||
interface ImportServersBtnConnectProps extends ImportServersBtnProps {
|
interface ImportServersBtnConnectProps extends ImportServersBtnProps {
|
||||||
createServers: (servers: ServerData[]) => void;
|
createServers: (servers: ServerData[]) => void;
|
||||||
servers: ServersMap;
|
servers: ServersMap;
|
||||||
fileRef: Ref<HTMLInputElement>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const serversFiltering = (servers: ServerData[]) =>
|
const serversFiltering = (servers: ServerData[]) =>
|
||||||
@ -30,14 +28,13 @@ const serversFiltering = (servers: ServerData[]) =>
|
|||||||
export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC<ImportServersBtnConnectProps> => ({
|
export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC<ImportServersBtnConnectProps> => ({
|
||||||
createServers,
|
createServers,
|
||||||
servers,
|
servers,
|
||||||
fileRef,
|
|
||||||
children,
|
children,
|
||||||
onImport = () => {},
|
onImport = () => {},
|
||||||
onImportError = () => {},
|
onImportError = () => {},
|
||||||
tooltipPlacement = 'bottom',
|
tooltipPlacement = 'bottom',
|
||||||
className = '',
|
className = '',
|
||||||
}) => {
|
}) => {
|
||||||
const ref = fileRef ?? useRef<HTMLInputElement>();
|
const ref = useRef<HTMLInputElement>();
|
||||||
const [serversToCreate, setServersToCreate] = useState<ServerData[] | undefined>();
|
const [serversToCreate, setServersToCreate] = useState<ServerData[] | undefined>();
|
||||||
const [duplicatedServers, setDuplicatedServers] = useState<ServerData[]>([]);
|
const [duplicatedServers, setDuplicatedServers] = useState<ServerData[]>([]);
|
||||||
const [isModalOpen,, showModal, hideModal] = useToggle();
|
const [isModalOpen,, showModal, hideModal] = useToggle();
|
||||||
@ -78,7 +75,13 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
|
|||||||
You can create servers by importing a CSV file with columns <b>name</b>, <b>apiKey</b> and <b>url</b>.
|
You can create servers by importing a CSV file with columns <b>name</b>, <b>apiKey</b> and <b>url</b>.
|
||||||
</UncontrolledTooltip>
|
</UncontrolledTooltip>
|
||||||
|
|
||||||
<input type="file" accept="text/csv" className="import-servers-btn__csv-select" ref={ref} onChange={onFile} />
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="text/csv"
|
||||||
|
className="import-servers-btn__csv-select"
|
||||||
|
ref={mutableRefToElementRef(ref)}
|
||||||
|
onChange={onFile}
|
||||||
|
/>
|
||||||
|
|
||||||
<DuplicatedServersModal
|
<DuplicatedServersModal
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { ShortUrl } from '../data';
|
|||||||
import { SelectedServer } from '../../servers/data';
|
import { SelectedServer } from '../../servers/data';
|
||||||
import { ShortUrlDetailLink } from './ShortUrlDetailLink';
|
import { ShortUrlDetailLink } from './ShortUrlDetailLink';
|
||||||
import './ShortUrlVisitsCount.scss';
|
import './ShortUrlVisitsCount.scss';
|
||||||
|
import { mutableRefToElementRef } from '../../utils/helpers/components';
|
||||||
|
|
||||||
interface ShortUrlVisitsCountProps {
|
interface ShortUrlVisitsCountProps {
|
||||||
shortUrl?: ShortUrl | null;
|
shortUrl?: ShortUrl | null;
|
||||||
@ -35,7 +36,7 @@ export const ShortUrlVisitsCount = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const prettifiedMaxVisits = prettify(maxVisits);
|
const prettifiedMaxVisits = prettify(maxVisits);
|
||||||
const tooltipRef = useRef<HTMLElement | null>();
|
const tooltipRef = useRef<HTMLElement | undefined>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -43,9 +44,7 @@ export const ShortUrlVisitsCount = (
|
|||||||
{visitsLink}
|
{visitsLink}
|
||||||
<small
|
<small
|
||||||
className="short-urls-visits-count__max-visits-control"
|
className="short-urls-visits-count__max-visits-control"
|
||||||
ref={(el) => {
|
ref={mutableRefToElementRef(tooltipRef)}
|
||||||
tooltipRef.current = el;
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{' '}/ {prettifiedMaxVisits}{' '}
|
{' '}/ {prettifiedMaxVisits}{' '}
|
||||||
<sup>
|
<sup>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { getServerId, SelectedServer } from '../servers/data';
|
|||||||
import { TagBullet } from './helpers/TagBullet';
|
import { TagBullet } from './helpers/TagBullet';
|
||||||
import { NormalizedTag, TagModalProps } from './data';
|
import { NormalizedTag, TagModalProps } from './data';
|
||||||
import './TagCard.scss';
|
import './TagCard.scss';
|
||||||
|
import { mutableRefToElementRef } from '../utils/helpers/components';
|
||||||
|
|
||||||
export interface TagCardProps {
|
export interface TagCardProps {
|
||||||
tag: NormalizedTag;
|
tag: NormalizedTag;
|
||||||
@ -28,7 +29,7 @@ export const TagCard = (
|
|||||||
const [isDeleteModalOpen, toggleDelete] = useToggle();
|
const [isDeleteModalOpen, toggleDelete] = useToggle();
|
||||||
const [isEditModalOpen, toggleEdit] = useToggle();
|
const [isEditModalOpen, toggleEdit] = useToggle();
|
||||||
const [hasTitle,, displayTitle] = useToggle();
|
const [hasTitle,, displayTitle] = useToggle();
|
||||||
const titleRef = useRef<HTMLElement>();
|
const titleRef = useRef<HTMLHeadingElement | undefined>();
|
||||||
const serverId = getServerId(selectedServer);
|
const serverId = getServerId(selectedServer);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -55,9 +56,7 @@ export const TagCard = (
|
|||||||
<h5
|
<h5
|
||||||
className="tag-card__tag-title text-ellipsis"
|
className="tag-card__tag-title text-ellipsis"
|
||||||
title={hasTitle ? tag.tag : undefined}
|
title={hasTitle ? tag.tag : undefined}
|
||||||
ref={(el) => {
|
ref={mutableRefToElementRef(titleRef)}
|
||||||
titleRef.current = el ?? undefined;
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<TagBullet tag={tag.tag} colorGenerator={colorGenerator} />
|
<TagBullet tag={tag.tag} colorGenerator={colorGenerator} />
|
||||||
<span className="tag-card__tag-name" onClick={toggle}>{tag.tag}</span>
|
<span className="tag-card__tag-name" onClick={toggle}>{tag.tag}</span>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|||||||
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import { Placement } from '@popperjs/core';
|
import { Placement } from '@popperjs/core';
|
||||||
|
import { mutableRefToElementRef } from './helpers/components';
|
||||||
|
|
||||||
type InfoTooltipProps = PropsWithChildren<{
|
type InfoTooltipProps = PropsWithChildren<{
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -10,14 +11,11 @@ type InfoTooltipProps = PropsWithChildren<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const InfoTooltip: FC<InfoTooltipProps> = ({ className = '', placement, children }) => {
|
export const InfoTooltip: FC<InfoTooltipProps> = ({ className = '', placement, children }) => {
|
||||||
const ref = useRef<HTMLSpanElement | null>();
|
const ref = useRef<HTMLSpanElement | undefined>();
|
||||||
const refCallback = (el: HTMLSpanElement) => {
|
|
||||||
ref.current = el;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className={className} ref={refCallback}>
|
<span className={className} ref={mutableRefToElementRef(ref)}>
|
||||||
<FontAwesomeIcon icon={infoIcon} />
|
<FontAwesomeIcon icon={infoIcon} />
|
||||||
</span>
|
</span>
|
||||||
<UncontrolledTooltip target={(() => ref.current) as any} placement={placement}>{children}</UncontrolledTooltip>
|
<UncontrolledTooltip target={(() => ref.current) as any} placement={placement}>{children}</UncontrolledTooltip>
|
||||||
|
|||||||
5
src/utils/helpers/components.ts
Normal file
5
src/utils/helpers/components.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { MutableRefObject, Ref } from 'react';
|
||||||
|
|
||||||
|
export const mutableRefToElementRef = <T>(ref: MutableRefObject<T | undefined>): Ref<T> => (el) => {
|
||||||
|
ref.current = el ?? undefined; // eslint-disable-line no-param-reassign
|
||||||
|
};
|
||||||
@ -1,32 +1,23 @@
|
|||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Card, CardText, CardTitle } from 'reactstrap';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { HighlightCard, HighlightCardProps } from '../../../src/servers/helpers/HighlightCard';
|
import { HighlightCard, HighlightCardProps } from '../../../src/servers/helpers/HighlightCard';
|
||||||
|
|
||||||
describe('<HighlightCard />', () => {
|
describe('<HighlightCard />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
const setUp = (props: HighlightCardProps & { children?: ReactNode }) => render(
|
||||||
const createWrapper = (props: HighlightCardProps & { children?: ReactNode }) => {
|
<MemoryRouter>
|
||||||
wrapper = shallow(<HighlightCard {...props} />);
|
<HighlightCard {...props} />
|
||||||
|
</MemoryRouter>,
|
||||||
return wrapper;
|
);
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => wrapper?.unmount());
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined],
|
[undefined],
|
||||||
[false],
|
[false],
|
||||||
])('renders expected components', (link) => {
|
])('does not render icon when there is no link', (link) => {
|
||||||
const wrapper = createWrapper({ title: 'foo', link: link as undefined | false });
|
setUp({ title: 'foo', link: link as undefined | false });
|
||||||
|
|
||||||
expect(wrapper.find(Card)).toHaveLength(1);
|
expect(screen.queryByRole('img', { hidden: true })).not.toBeInTheDocument();
|
||||||
expect(wrapper.find(CardTitle)).toHaveLength(1);
|
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
||||||
expect(wrapper.find(CardText)).toHaveLength(1);
|
|
||||||
expect(wrapper.find(FontAwesomeIcon)).toHaveLength(0);
|
|
||||||
expect(wrapper.prop('tag')).not.toEqual(Link);
|
|
||||||
expect(wrapper.prop('to')).not.toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -34,10 +25,8 @@ describe('<HighlightCard />', () => {
|
|||||||
['bar'],
|
['bar'],
|
||||||
['baz'],
|
['baz'],
|
||||||
])('renders provided title', (title) => {
|
])('renders provided title', (title) => {
|
||||||
const wrapper = createWrapper({ title });
|
setUp({ title });
|
||||||
const cardTitle = wrapper.find(CardTitle);
|
expect(screen.getByText(title)).toHaveAttribute('class', expect.stringContaining('highlight-card__title'));
|
||||||
|
|
||||||
expect(cardTitle.html()).toContain(`>${title}<`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -45,10 +34,8 @@ describe('<HighlightCard />', () => {
|
|||||||
['bar'],
|
['bar'],
|
||||||
['baz'],
|
['baz'],
|
||||||
])('renders provided children', (children) => {
|
])('renders provided children', (children) => {
|
||||||
const wrapper = createWrapper({ title: 'foo', children });
|
setUp({ title: 'title', children });
|
||||||
const cardText = wrapper.find(CardText);
|
expect(screen.getByText(children)).toHaveAttribute('class', expect.stringContaining('card-text'));
|
||||||
|
|
||||||
expect(cardText.html()).toContain(`>${children}<`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -56,10 +43,9 @@ describe('<HighlightCard />', () => {
|
|||||||
['bar'],
|
['bar'],
|
||||||
['baz'],
|
['baz'],
|
||||||
])('adds extra props when a link is provided', (link) => {
|
])('adds extra props when a link is provided', (link) => {
|
||||||
const wrapper = createWrapper({ title: 'foo', link });
|
setUp({ title: 'title', link });
|
||||||
|
|
||||||
expect(wrapper.find(FontAwesomeIcon)).toHaveLength(1);
|
expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument();
|
||||||
expect(wrapper.prop('tag')).toEqual(Link);
|
expect(screen.getByRole('link')).toHaveAttribute('href', `/${link}`);
|
||||||
expect(wrapper.prop('to')).toEqual(link);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,46 +1,41 @@
|
|||||||
import { ReactNode } from 'react';
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import {
|
import {
|
||||||
ImportServersBtn as createImportServersBtn,
|
ImportServersBtn as createImportServersBtn,
|
||||||
ImportServersBtnProps,
|
ImportServersBtnProps,
|
||||||
} from '../../../src/servers/helpers/ImportServersBtn';
|
} from '../../../src/servers/helpers/ImportServersBtn';
|
||||||
import { ServersImporter } from '../../../src/servers/services/ServersImporter';
|
import { ServersImporter } from '../../../src/servers/services/ServersImporter';
|
||||||
import { DuplicatedServersModal } from '../../../src/servers/helpers/DuplicatedServersModal';
|
import { ServersMap, ServerWithId } from '../../../src/servers/data';
|
||||||
|
|
||||||
describe('<ImportServersBtn />', () => {
|
describe('<ImportServersBtn />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
|
||||||
const onImportMock = jest.fn();
|
const onImportMock = jest.fn();
|
||||||
const createServersMock = jest.fn();
|
const createServersMock = jest.fn();
|
||||||
const importServersFromFile = jest.fn().mockResolvedValue([]);
|
const importServersFromFile = jest.fn().mockResolvedValue([]);
|
||||||
const serversImporterMock = Mock.of<ServersImporter>({ importServersFromFile });
|
const serversImporterMock = Mock.of<ServersImporter>({ importServersFromFile });
|
||||||
const click = jest.fn();
|
|
||||||
const fileRef = { current: Mock.of<HTMLInputElement>({ click }) };
|
|
||||||
const ImportServersBtn = createImportServersBtn(serversImporterMock);
|
const ImportServersBtn = createImportServersBtn(serversImporterMock);
|
||||||
const createWrapper = (props: Partial<ImportServersBtnProps & { children: ReactNode }> = {}) => {
|
const setUp = (props: Partial<ImportServersBtnProps> = {}, servers: ServersMap = {}) => ({
|
||||||
wrapper = shallow(
|
user: userEvent.setup(),
|
||||||
|
...render(
|
||||||
<ImportServersBtn
|
<ImportServersBtn
|
||||||
servers={{}}
|
servers={servers}
|
||||||
{...props}
|
{...props}
|
||||||
fileRef={fileRef}
|
|
||||||
createServers={createServersMock}
|
createServers={createServersMock}
|
||||||
onImport={onImportMock}
|
onImport={onImportMock}
|
||||||
/>,
|
/>,
|
||||||
);
|
),
|
||||||
|
});
|
||||||
return wrapper;
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
afterEach(jest.clearAllMocks);
|
||||||
afterEach(() => wrapper.unmount());
|
|
||||||
|
|
||||||
it('renders a button, a tooltip and a file input', () => {
|
it('shows tooltip on button hover', async () => {
|
||||||
const wrapper = createWrapper();
|
const { user } = setUp();
|
||||||
|
|
||||||
expect(wrapper.find('#importBtn')).toHaveLength(1);
|
expect(screen.queryByText(/^You can create servers by importing a CSV file/)).not.toBeInTheDocument();
|
||||||
expect(wrapper.find(UncontrolledTooltip)).toHaveLength(1);
|
await user.hover(screen.getByRole('button'));
|
||||||
expect(wrapper.find('.import-servers-btn__csv-select')).toHaveLength(1);
|
await waitFor(
|
||||||
|
() => expect(screen.getByText(/^You can create servers by importing a CSV file/)).toBeInTheDocument(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -48,53 +43,43 @@ describe('<ImportServersBtn />', () => {
|
|||||||
['foo', 'foo'],
|
['foo', 'foo'],
|
||||||
['bar', 'bar'],
|
['bar', 'bar'],
|
||||||
])('allows a class name to be provided', (providedClassName, expectedClassName) => {
|
])('allows a class name to be provided', (providedClassName, expectedClassName) => {
|
||||||
const wrapper = createWrapper({ className: providedClassName });
|
setUp({ className: providedClassName });
|
||||||
|
expect(screen.getByRole('button')).toHaveAttribute('class', expect.stringContaining(expectedClassName));
|
||||||
expect(wrapper.find('#importBtn').prop('className')).toEqual(expectedClassName);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined, true],
|
[undefined, 'Import from file'],
|
||||||
['foo', false],
|
['foo', 'foo'],
|
||||||
['bar', false],
|
['bar', 'bar'],
|
||||||
])('has expected text', (children, expectToHaveDefaultText) => {
|
])('has expected text', (children, expectedText) => {
|
||||||
const wrapper = createWrapper({ children });
|
setUp({ children });
|
||||||
|
expect(screen.getByRole('button')).toHaveTextContent(expectedText);
|
||||||
if (expectToHaveDefaultText) {
|
|
||||||
expect(wrapper.find('#importBtn').html()).toContain('Import from file');
|
|
||||||
} else {
|
|
||||||
expect(wrapper.find('#importBtn').html()).toContain(children);
|
|
||||||
expect(wrapper.find('#importBtn').html()).not.toContain('Import from file');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('triggers click on file ref when button is clicked', () => {
|
|
||||||
const wrapper = createWrapper();
|
|
||||||
const btn = wrapper.find('#importBtn');
|
|
||||||
|
|
||||||
btn.simulate('click');
|
|
||||||
|
|
||||||
expect(click).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('imports servers when file input changes', async () => {
|
it('imports servers when file input changes', async () => {
|
||||||
const wrapper = createWrapper();
|
const { container } = setUp();
|
||||||
const file = wrapper.find('.import-servers-btn__csv-select');
|
const input = container.querySelector('[type=file]');
|
||||||
|
|
||||||
await file.simulate('change', { target: { files: [''] } });
|
|
||||||
|
|
||||||
|
input && fireEvent.change(input, { target: { files: [''] } });
|
||||||
expect(importServersFromFile).toHaveBeenCalledTimes(1);
|
expect(importServersFromFile).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['discard'],
|
['Save anyway', true],
|
||||||
['save'],
|
['Discard', false],
|
||||||
])('invokes callback in DuplicatedServersModal events', (event) => {
|
])('creates expected servers depending on selected option in modal', async (btnName, savesDuplicatedServers) => {
|
||||||
const wrapper = createWrapper();
|
const existingServer = Mock.of<ServerWithId>({ id: 'abc', url: 'existingUrl', apiKey: 'existingApiKey' });
|
||||||
|
const newServer = Mock.of<ServerWithId>({ url: 'newUrl', apiKey: 'newApiKey' });
|
||||||
|
const { container, user } = setUp({}, { abc: existingServer });
|
||||||
|
const input = container.querySelector('[type=file]');
|
||||||
|
importServersFromFile.mockResolvedValue([existingServer, newServer]);
|
||||||
|
|
||||||
wrapper.find(DuplicatedServersModal).simulate(event);
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||||
|
input && fireEvent.change(input, { target: { files: [''] } });
|
||||||
|
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
|
||||||
|
await user.click(screen.getByRole('button', { name: btnName }));
|
||||||
|
|
||||||
expect(createServersMock).toHaveBeenCalledTimes(1);
|
expect(createServersMock).toHaveBeenCalledWith(savesDuplicatedServers ? [existingServer, newServer] : [newServer]);
|
||||||
expect(onImportMock).toHaveBeenCalledTimes(1);
|
expect(onImportMock).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,29 +1,48 @@
|
|||||||
import { shallow } from 'enzyme';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Route } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
import { Settings as createSettings } from '../../src/settings/Settings';
|
import { Settings as createSettings } from '../../src/settings/Settings';
|
||||||
import { NoMenuLayout } from '../../src/common/NoMenuLayout';
|
|
||||||
import { NavPillItem } from '../../src/utils/NavPills';
|
|
||||||
|
|
||||||
describe('<Settings />', () => {
|
describe('<Settings />', () => {
|
||||||
const Component = () => null;
|
const Settings = createSettings(
|
||||||
const Settings = createSettings(Component, Component, Component, Component, Component, Component);
|
() => <span>RealTimeUpdates</span>,
|
||||||
|
() => <span>ShortUrlCreation</span>,
|
||||||
|
() => <span>ShortUrlsList</span>,
|
||||||
|
() => <span>UserInterface</span>,
|
||||||
|
() => <span>Visits</span>,
|
||||||
|
() => <span>Tags</span>,
|
||||||
|
);
|
||||||
|
const setUp = (activeRoute = '/') => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
history.push(activeRoute);
|
||||||
|
return render(<Router location={history.location} navigator={history}><Settings /></Router>);
|
||||||
|
};
|
||||||
|
|
||||||
it('renders a no-menu layout with the expected settings sections', () => {
|
it.each([
|
||||||
const wrapper = shallow(<Settings />);
|
['/general', {
|
||||||
const layout = wrapper.find(NoMenuLayout);
|
visibleComps: ['UserInterface', 'RealTimeUpdates'],
|
||||||
const sections = wrapper.find(Route);
|
hiddenComps: ['ShortUrlCreation', 'ShortUrlsList', 'Tags', 'Visits'],
|
||||||
|
}],
|
||||||
|
['/short-urls', {
|
||||||
|
visibleComps: ['ShortUrlCreation', 'ShortUrlsList'],
|
||||||
|
hiddenComps: ['UserInterface', 'RealTimeUpdates', 'Tags', 'Visits'],
|
||||||
|
}],
|
||||||
|
['/other-items', {
|
||||||
|
visibleComps: ['Tags', 'Visits'],
|
||||||
|
hiddenComps: ['UserInterface', 'RealTimeUpdates', 'ShortUrlCreation', 'ShortUrlsList'],
|
||||||
|
}],
|
||||||
|
])('renders expected sections based on route', (activeRoute, { visibleComps, hiddenComps }) => {
|
||||||
|
setUp(activeRoute);
|
||||||
|
|
||||||
expect(layout).toHaveLength(1);
|
visibleComps.forEach((comp) => expect(screen.getByText(comp)).toBeInTheDocument());
|
||||||
expect(sections).toHaveLength(4);
|
hiddenComps.forEach((comp) => expect(screen.queryByText(comp)).not.toBeInTheDocument());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders expected menu', () => {
|
it('renders expected menu', () => {
|
||||||
const wrapper = shallow(<Settings />);
|
setUp();
|
||||||
const items = wrapper.find(NavPillItem);
|
|
||||||
|
|
||||||
expect(items).toHaveLength(3);
|
expect(screen.getByRole('link', { name: 'General' })).toHaveAttribute('href', '/general');
|
||||||
expect(items.first().prop('to')).toEqual('general');
|
expect(screen.getByRole('link', { name: 'Short URLs' })).toHaveAttribute('href', '/short-urls');
|
||||||
expect(items.at(1).prop('to')).toEqual('short-urls');
|
expect(screen.getByRole('link', { name: 'Other items' })).toHaveAttribute('href', '/other-items');
|
||||||
expect(items.last().prop('to')).toEqual('other-items');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user