mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Merge pull request #640 from acelaya-forks/feature/more-rtl-tests
Feature/more rtl tests
This commit is contained in:
commit
686fe5abbe
25 changed files with 870 additions and 383 deletions
|
@ -3,3 +3,5 @@ import 'jest-canvas-mock';
|
|||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
(global as any).ResizeObserver = ResizeObserver;
|
||||
(global as any).scrollTo = () => {};
|
||||
(global as any).matchMedia = (media: string) => ({ matches: false, media });
|
||||
|
|
|
@ -9,10 +9,10 @@ module.exports = {
|
|||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 85,
|
||||
statements: 90,
|
||||
branches: 80,
|
||||
functions: 80,
|
||||
lines: 85,
|
||||
functions: 85,
|
||||
lines: 90,
|
||||
},
|
||||
},
|
||||
setupFiles: ['<rootDir>/config/jest/setupBeforeEnzyme.js', '<rootDir>/config/jest/setupEnzyme.js'],
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"build:serve": "serve -p 5000 ./build",
|
||||
"test": "jest --env=jsdom --colors --verbose",
|
||||
"test:coverage": "npm run test -- --coverage --coverageReporters=text --coverageReporters=text-summary",
|
||||
"test:ci": "npm run test:coverage -- --coverageReporters=clover",
|
||||
"test:ci": "npm run test:coverage -- --coverageReporters=clover --ci",
|
||||
"test:pretty": "npm run test:coverage -- --coverageReporters=html",
|
||||
"mutate": "./node_modules/.bin/stryker run --concurrency 4 --ignoreStatic"
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { FC, PropsWithChildren, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
const ScrollToTop = (): FC<PropsWithChildren<unknown>> => ({ children }) => {
|
||||
export const ScrollToTop: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -10,5 +10,3 @@ const ScrollToTop = (): FC<PropsWithChildren<unknown>> => ({ children }) => {
|
|||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default ScrollToTop;
|
||||
|
|
|
@ -17,7 +17,9 @@ interface SimplePaginatorProps {
|
|||
centered?: boolean;
|
||||
}
|
||||
|
||||
const SimplePaginator: FC<SimplePaginatorProps> = ({ pagesCount, currentPage, setCurrentPage, centered = true }) => {
|
||||
export const SimplePaginator: FC<SimplePaginatorProps> = (
|
||||
{ pagesCount, currentPage, setCurrentPage, centered = true },
|
||||
) => {
|
||||
if (pagesCount < 2) {
|
||||
return null;
|
||||
}
|
||||
|
@ -35,7 +37,9 @@ const SimplePaginator: FC<SimplePaginatorProps> = ({ pagesCount, currentPage, se
|
|||
disabled={pageIsEllipsis(pageNumber)}
|
||||
active={currentPage === pageNumber}
|
||||
>
|
||||
<PaginationLink tag="span" onClick={onClick(pageNumber)}>{prettifyPageNumber(pageNumber)}</PaginationLink>
|
||||
<PaginationLink role="link" tag="span" onClick={onClick(pageNumber)}>
|
||||
{prettifyPageNumber(pageNumber)}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
<PaginationItem disabled={currentPage >= pagesCount}>
|
||||
|
@ -44,5 +48,3 @@ const SimplePaginator: FC<SimplePaginatorProps> = ({ pagesCount, currentPage, se
|
|||
</Pagination>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimplePaginator;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import axios from 'axios';
|
||||
import Bottle from 'bottlejs';
|
||||
import ScrollToTop from '../ScrollToTop';
|
||||
import { ScrollToTop } from '../ScrollToTop';
|
||||
import { MainHeader } from '../MainHeader';
|
||||
import { Home } from '../Home';
|
||||
import { MenuLayout } from '../MenuLayout';
|
||||
|
@ -23,7 +23,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
bottle.service('ReportExporter', ReportExporter, 'window', 'jsonToCsv');
|
||||
|
||||
// Components
|
||||
bottle.serviceFactory('ScrollToTop', ScrollToTop);
|
||||
bottle.serviceFactory('ScrollToTop', () => ScrollToTop);
|
||||
|
||||
bottle.serviceFactory('MainHeader', MainHeader, 'ServersDropdown');
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ export const DomainSelector = ({ listDomains, value, domainsList, onChange }: Do
|
|||
outline
|
||||
type="button"
|
||||
className="domains-dropdown__back-btn"
|
||||
aria-label="Back to domains list"
|
||||
onClick={pipe(unselectDomain, hideInput)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faUndo} />
|
||||
|
|
|
@ -2,7 +2,7 @@ import { FC, useEffect, useRef } from 'react';
|
|||
import { splitEvery } from 'ramda';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SimpleCard } from '../utils/SimpleCard';
|
||||
import SimplePaginator from '../common/SimplePaginator';
|
||||
import { SimplePaginator } from '../common/SimplePaginator';
|
||||
import { useQueryState } from '../utils/helpers/hooks';
|
||||
import { parseQuery } from '../utils/helpers/query';
|
||||
import { TableOrderIcon } from '../utils/table/TableOrderIcon';
|
||||
|
|
|
@ -4,7 +4,7 @@ import { min, splitEvery } from 'ramda';
|
|||
import { faCheck as checkIcon, faRobot as botIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { UncontrolledTooltip } from 'reactstrap';
|
||||
import SimplePaginator from '../common/SimplePaginator';
|
||||
import { SimplePaginator } from '../common/SimplePaginator';
|
||||
import SearchField from '../utils/SearchField';
|
||||
import { determineOrderDir, Order, sortList } from '../utils/helpers/ordering';
|
||||
import { prettify } from '../utils/helpers/numbers';
|
||||
|
|
|
@ -2,7 +2,7 @@ import { FC, useState } from 'react';
|
|||
import { fromPairs, pipe, reverse, sortBy, splitEvery, toLower, toPairs, type, zipObj } from 'ramda';
|
||||
import { rangeOf } from '../../utils/utils';
|
||||
import { Order } from '../../utils/helpers/ordering';
|
||||
import SimplePaginator from '../../common/SimplePaginator';
|
||||
import { SimplePaginator } from '../../common/SimplePaginator';
|
||||
import { roundTen } from '../../utils/helpers/numbers';
|
||||
import { OrderingDropdown } from '../../utils/OrderingDropdown';
|
||||
import PaginationDropdown from '../../utils/PaginationDropdown';
|
||||
|
|
|
@ -1,47 +1,32 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { NotFound } from '../../src/common/NotFound';
|
||||
import { SimpleCard } from '../../src/utils/SimpleCard';
|
||||
|
||||
describe('<NotFound />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (props = {}) => {
|
||||
wrapper = shallow(<NotFound {...props} />).find(SimpleCard);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
const setUp = (props = {}) => render(<MemoryRouter><NotFound {...props} /></MemoryRouter>);
|
||||
|
||||
it('shows expected error title', () => {
|
||||
const wrapper = createWrapper();
|
||||
|
||||
expect(wrapper.contains('Oops! We could not find requested route.')).toEqual(true);
|
||||
setUp();
|
||||
expect(screen.getByText('Oops! We could not find requested route.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows expected error message', () => {
|
||||
const wrapper = createWrapper();
|
||||
|
||||
expect(wrapper.contains(
|
||||
setUp();
|
||||
expect(screen.getByText(
|
||||
'Use your browser\'s back button to navigate to the page you have previously come from, or just press this button.',
|
||||
)).toEqual(true);
|
||||
)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a link to the home', () => {
|
||||
const wrapper = createWrapper();
|
||||
const link = wrapper.find(Link);
|
||||
it.each([
|
||||
[{}, '/', 'Home'],
|
||||
[{ to: '/foo/bar', children: 'Hello' }, '/foo/bar', 'Hello'],
|
||||
[{ to: '/baz-bar', children: <>Foo</> }, '/baz-bar', 'Foo'],
|
||||
])('shows expected link and text', (props, expectedLink, expectedText) => {
|
||||
setUp(props);
|
||||
const link = screen.getByRole('link');
|
||||
|
||||
expect(link.prop('to')).toEqual('/');
|
||||
expect(link.prop('className')).toEqual('btn btn-outline-primary btn-lg');
|
||||
expect(link.prop('children')).toEqual('Home');
|
||||
});
|
||||
|
||||
it('shows a link with provided props', () => {
|
||||
const wrapper = createWrapper({ to: '/foo/bar', children: 'Hello' });
|
||||
const link = wrapper.find(Link);
|
||||
|
||||
expect(link.prop('to')).toEqual('/foo/bar');
|
||||
expect(link.prop('className')).toEqual('btn btn-outline-primary btn-lg');
|
||||
expect(link.prop('children')).toEqual('Hello');
|
||||
expect(link).toHaveAttribute('href', expectedLink);
|
||||
expect(link).toHaveTextContent(expectedText);
|
||||
expect(link).toHaveAttribute('class', 'btn btn-outline-primary btn-lg');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import createScrollToTop from '../../src/common/ScrollToTop';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn().mockReturnValue({}),
|
||||
}));
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { ScrollToTop } from '../../src/common/ScrollToTop';
|
||||
|
||||
describe('<ScrollToTop />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
const ScrollToTop = createScrollToTop();
|
||||
|
||||
wrapper = shallow(<ScrollToTop>Foobar</ScrollToTop>);
|
||||
it.each([
|
||||
['Foobar'],
|
||||
['Barfoo'],
|
||||
['Something'],
|
||||
])('just renders children', (children) => {
|
||||
render(<MemoryRouter><ScrollToTop>{children}</ScrollToTop></MemoryRouter>);
|
||||
expect(screen.getByText(children)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
afterEach(() => wrapper.unmount());
|
||||
|
||||
it('just renders children', () => expect(wrapper.text()).toEqual('Foobar'));
|
||||
});
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import ShlinkVersions, { ShlinkVersionsProps } from '../../src/common/ShlinkVersions';
|
||||
import { NonReachableServer, NotFoundServer, ReachableServer } from '../../src/servers/data';
|
||||
|
||||
describe('<ShlinkVersions />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (props: ShlinkVersionsProps) => {
|
||||
wrapper = shallow(<ShlinkVersions {...props} />);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
const setUp = (props: ShlinkVersionsProps) => render(<ShlinkVersions {...props} />);
|
||||
|
||||
it.each([
|
||||
['1.2.3', Mock.of<ReachableServer>({ version: '1.0.0', printableVersion: 'foo' }), 'v1.2.3', 'foo'],
|
||||
|
@ -22,15 +15,19 @@ describe('<ShlinkVersions />', () => {
|
|||
])(
|
||||
'displays expected versions when selected server is reachable',
|
||||
(clientVersion, selectedServer, expectedClientVersion, expectedServerVersion) => {
|
||||
const wrapper = createWrapper({ clientVersion, selectedServer });
|
||||
const links = wrapper.find('VersionLink');
|
||||
const serverLink = links.at(0);
|
||||
const clientLink = links.at(1);
|
||||
setUp({ clientVersion, selectedServer });
|
||||
const [serverLink, clientLink] = screen.getAllByRole('link');
|
||||
|
||||
expect(serverLink.prop('project')).toEqual('shlink');
|
||||
expect(serverLink.prop('version')).toEqual(expectedServerVersion);
|
||||
expect(clientLink.prop('project')).toEqual('shlink-web-client');
|
||||
expect(clientLink.prop('version')).toEqual(expectedClientVersion);
|
||||
expect(serverLink).toHaveAttribute(
|
||||
'href',
|
||||
`https://github.com/shlinkio/shlink/releases/${expectedServerVersion}`,
|
||||
);
|
||||
expect(serverLink).toHaveTextContent(expectedServerVersion);
|
||||
expect(clientLink).toHaveAttribute(
|
||||
'href',
|
||||
`https://github.com/shlinkio/shlink-web-client/releases/${expectedClientVersion}`,
|
||||
);
|
||||
expect(clientLink).toHaveTextContent(expectedClientVersion);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -39,10 +36,10 @@ describe('<ShlinkVersions />', () => {
|
|||
['1.2.3', Mock.of<NotFoundServer>({ serverNotFound: true })],
|
||||
['1.2.3', Mock.of<NonReachableServer>({ serverNotReachable: true })],
|
||||
])('displays only client version when selected server is not reachable', (clientVersion, selectedServer) => {
|
||||
const wrapper = createWrapper({ clientVersion, selectedServer });
|
||||
const links = wrapper.find('VersionLink');
|
||||
setUp({ clientVersion, selectedServer });
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
expect(links).toHaveLength(1);
|
||||
expect(links.at(0).prop('project')).toEqual('shlink-web-client');
|
||||
expect(links[0]).toHaveAttribute('href', 'https://github.com/shlinkio/shlink-web-client/releases/v1.2.3');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,26 +1,19 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { render } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import ShlinkVersionsContainer from '../../src/common/ShlinkVersionsContainer';
|
||||
import { SelectedServer } from '../../src/servers/data';
|
||||
import { Sidebar } from '../../src/common/reducers/sidebar';
|
||||
|
||||
describe('<ShlinkVersionsContainer />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
const createWrapper = (sidebar: Sidebar) => {
|
||||
wrapper = shallow(<ShlinkVersionsContainer selectedServer={Mock.all<SelectedServer>()} sidebar={sidebar} />);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
const setUp = (sidebar: Sidebar) => render(
|
||||
<ShlinkVersionsContainer selectedServer={Mock.all<SelectedServer>()} sidebar={sidebar} />,
|
||||
);
|
||||
|
||||
it.each([
|
||||
[{ sidebarPresent: false }, 'text-center'],
|
||||
[{ sidebarPresent: true }, 'text-center shlink-versions-container--with-sidebar'],
|
||||
])('renders proper col classes based on sidebar status', (sidebar, expectedClasses) => {
|
||||
const wrapper = createWrapper(sidebar);
|
||||
|
||||
expect(wrapper.find('div').prop('className')).toEqual(`${expectedClasses}`);
|
||||
const { container } = setUp(sidebar);
|
||||
expect(container.firstChild).toHaveAttribute('class', `${expectedClasses}`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,28 +1,23 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { identity } from 'ramda';
|
||||
import { PaginationItem } from 'reactstrap';
|
||||
import SimplePaginator from '../../src/common/SimplePaginator';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { SimplePaginator } from '../../src/common/SimplePaginator';
|
||||
import { ELLIPSIS } from '../../src/utils/helpers/pagination';
|
||||
|
||||
describe('<SimplePaginator />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (pagesCount: number, currentPage = 1) => {
|
||||
wrapper = shallow(<SimplePaginator pagesCount={pagesCount} currentPage={currentPage} setCurrentPage={identity} />);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
const setUp = (pagesCount: number, currentPage = 1) => render(
|
||||
<SimplePaginator pagesCount={pagesCount} currentPage={currentPage} setCurrentPage={jest.fn()} />,
|
||||
);
|
||||
|
||||
it.each([-3, -2, 0, 1])('renders empty when the amount of pages is smaller than 2', (pagesCount) => {
|
||||
expect(createWrapper(pagesCount).text()).toEqual('');
|
||||
const { container } = setUp(pagesCount);
|
||||
expect(container.firstChild).toEqual(null);
|
||||
});
|
||||
|
||||
describe('ELLIPSIS are rendered where expected', () => {
|
||||
const getItemsForPages = (pagesCount: number, currentPage: number) => {
|
||||
const paginator = createWrapper(pagesCount, currentPage);
|
||||
const items = paginator.find(PaginationItem);
|
||||
const itemsWithEllipsis = items.filterWhere((item) => item?.key()?.includes(ELLIPSIS));
|
||||
setUp(pagesCount, currentPage);
|
||||
|
||||
const items = screen.getAllByRole('link');
|
||||
const itemsWithEllipsis = items.filter((item) => item.innerHTML.includes(ELLIPSIS));
|
||||
|
||||
return { items, itemsWithEllipsis };
|
||||
};
|
||||
|
@ -30,22 +25,22 @@ describe('<SimplePaginator />', () => {
|
|||
it('renders first ELLIPSIS', () => {
|
||||
const { items, itemsWithEllipsis } = getItemsForPages(9, 7);
|
||||
|
||||
expect(items.at(2).html()).toContain(ELLIPSIS);
|
||||
expect(items[1]).toHaveTextContent(ELLIPSIS);
|
||||
expect(itemsWithEllipsis).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders last ELLIPSIS', () => {
|
||||
const { items, itemsWithEllipsis } = getItemsForPages(9, 2);
|
||||
|
||||
expect(items.at(items.length - 3).html()).toContain(ELLIPSIS);
|
||||
expect(items[items.length - 2]).toHaveTextContent(ELLIPSIS);
|
||||
expect(itemsWithEllipsis).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders both ELLIPSIS', () => {
|
||||
const { items, itemsWithEllipsis } = getItemsForPages(20, 9);
|
||||
|
||||
expect(items.at(2).html()).toContain(ELLIPSIS);
|
||||
expect(items.at(items.length - 3).html()).toContain(ELLIPSIS);
|
||||
expect(items[1]).toHaveTextContent(ELLIPSIS);
|
||||
expect(items[items.length - 2]).toHaveTextContent(ELLIPSIS);
|
||||
expect(itemsWithEllipsis).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,25 +1,17 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ShlinkLogo, ShlinkLogoProps } from '../../../src/common/img/ShlinkLogo';
|
||||
import { MAIN_COLOR } from '../../../src/utils/theme';
|
||||
|
||||
describe('<ShlinkLogo />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (props: ShlinkLogoProps) => {
|
||||
wrapper = shallow(<ShlinkLogo {...props} />);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
const setUp = (props: ShlinkLogoProps) => render(<ShlinkLogo {...props} />);
|
||||
|
||||
it.each([
|
||||
[undefined, MAIN_COLOR],
|
||||
['red', 'red'],
|
||||
['white', 'white'],
|
||||
])('renders expected color', (color, expectedColor) => {
|
||||
const wrapper = createWrapper({ color });
|
||||
|
||||
expect(wrapper.find('g').prop('fill')).toEqual(expectedColor);
|
||||
const { container } = setUp({ color });
|
||||
expect(container.querySelector('g')).toHaveAttribute('fill', expectedColor);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
@ -27,8 +19,12 @@ describe('<ShlinkLogo />', () => {
|
|||
['foo', 'foo'],
|
||||
['bar', 'bar'],
|
||||
])('renders expected class', (className, expectedClassName) => {
|
||||
const wrapper = createWrapper({ className });
|
||||
const { container } = setUp({ className });
|
||||
|
||||
expect(wrapper.prop('className')).toEqual(expectedClassName);
|
||||
if (expectedClassName) {
|
||||
expect(container.firstChild).toHaveAttribute('class', expectedClassName);
|
||||
} else {
|
||||
expect(container.firstChild).not.toHaveAttribute('class');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { ShlinkDomainRedirects } from '../../src/api/types';
|
||||
import { DomainRow } from '../../src/domains/DomainRow';
|
||||
|
@ -6,42 +6,71 @@ import { SelectedServer } from '../../src/servers/data';
|
|||
import { Domain } from '../../src/domains/data';
|
||||
|
||||
describe('<DomainRow />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (domain: Domain, selectedServer = Mock.all<SelectedServer>()) => {
|
||||
wrapper = shallow(
|
||||
const redirectsCombinations = [
|
||||
[Mock.of<ShlinkDomainRedirects>({ baseUrlRedirect: 'foo' })],
|
||||
[Mock.of<ShlinkDomainRedirects>({ invalidShortUrlRedirect: 'bar' })],
|
||||
[Mock.of<ShlinkDomainRedirects>({ baseUrlRedirect: 'baz', regular404Redirect: 'foo' })],
|
||||
[
|
||||
Mock.of<ShlinkDomainRedirects>(
|
||||
{ baseUrlRedirect: 'baz', regular404Redirect: 'bar', invalidShortUrlRedirect: 'foo' },
|
||||
),
|
||||
],
|
||||
];
|
||||
const setUp = (domain: Domain, defaultRedirects?: ShlinkDomainRedirects) => render(
|
||||
<DomainRow
|
||||
domain={domain}
|
||||
selectedServer={selectedServer}
|
||||
defaultRedirects={defaultRedirects}
|
||||
selectedServer={Mock.all<SelectedServer>()}
|
||||
editDomainRedirects={jest.fn()}
|
||||
checkDomainHealth={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
it.each(redirectsCombinations)('shows expected redirects', (redirects) => {
|
||||
setUp(Mock.of<Domain>({ domain: '', isDefault: true, redirects }));
|
||||
const cells = screen.getAllByRole('cell');
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
redirects?.baseUrlRedirect && expect(cells[1]).toHaveTextContent(redirects.baseUrlRedirect);
|
||||
redirects?.regular404Redirect && expect(cells[2]).toHaveTextContent(redirects.regular404Redirect);
|
||||
redirects?.invalidShortUrlRedirect && expect(cells[3]).toHaveTextContent(redirects.invalidShortUrlRedirect);
|
||||
expect(screen.queryByText('(as fallback)')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[undefined, 3],
|
||||
[Mock.of<ShlinkDomainRedirects>(), 3],
|
||||
[Mock.of<ShlinkDomainRedirects>({ baseUrlRedirect: 'foo' }), 2],
|
||||
[Mock.of<ShlinkDomainRedirects>({ invalidShortUrlRedirect: 'foo' }), 2],
|
||||
[Mock.of<ShlinkDomainRedirects>({ baseUrlRedirect: 'foo', regular404Redirect: 'foo' }), 1],
|
||||
[
|
||||
Mock.of<ShlinkDomainRedirects>(
|
||||
{ baseUrlRedirect: 'foo', regular404Redirect: 'foo', invalidShortUrlRedirect: 'foo' },
|
||||
),
|
||||
0,
|
||||
],
|
||||
])('shows expected redirects', (redirects, expectedNoRedirects) => {
|
||||
const wrapper = createWrapper(Mock.of<Domain>({ domain: '', isDefault: true, redirects }));
|
||||
const noRedirects = wrapper.find('Nr');
|
||||
const cells = wrapper.find('td');
|
||||
[undefined],
|
||||
[Mock.of<ShlinkDomainRedirects>()],
|
||||
])('shows expected "no redirects"', (redirects) => {
|
||||
setUp(Mock.of<Domain>({ domain: '', isDefault: true, redirects }));
|
||||
const cells = screen.getAllByRole('cell');
|
||||
|
||||
expect(noRedirects).toHaveLength(expectedNoRedirects);
|
||||
redirects?.baseUrlRedirect && expect(cells.at(1).html()).toContain(redirects.baseUrlRedirect);
|
||||
redirects?.regular404Redirect && expect(cells.at(2).html()).toContain(redirects.regular404Redirect);
|
||||
redirects?.invalidShortUrlRedirect && expect(cells.at(3).html()).toContain(redirects.invalidShortUrlRedirect);
|
||||
expect(cells[1]).toHaveTextContent('No redirect');
|
||||
expect(cells[2]).toHaveTextContent('No redirect');
|
||||
expect(cells[3]).toHaveTextContent('No redirect');
|
||||
expect(screen.queryByText('(as fallback)')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each(redirectsCombinations)('shows expected fallback redirects', (fallbackRedirects) => {
|
||||
setUp(Mock.of<Domain>({ domain: '', isDefault: true }), fallbackRedirects);
|
||||
const cells = screen.getAllByRole('cell');
|
||||
|
||||
fallbackRedirects?.baseUrlRedirect && expect(cells[1]).toHaveTextContent(
|
||||
`${fallbackRedirects.baseUrlRedirect} (as fallback)`,
|
||||
);
|
||||
fallbackRedirects?.regular404Redirect && expect(cells[2]).toHaveTextContent(
|
||||
`${fallbackRedirects.regular404Redirect} (as fallback)`,
|
||||
);
|
||||
fallbackRedirects?.invalidShortUrlRedirect && expect(cells[3]).toHaveTextContent(
|
||||
`${fallbackRedirects.invalidShortUrlRedirect} (as fallback)`,
|
||||
);
|
||||
});
|
||||
|
||||
it.each([[true], [false]])('shows icon on default domain only', (isDefault) => {
|
||||
const { container } = setUp(Mock.of<Domain>({ domain: '', isDefault }));
|
||||
|
||||
if (isDefault) {
|
||||
expect(container.querySelector('#defaultDomainIcon')).toBeInTheDocument();
|
||||
} else {
|
||||
expect(container.querySelector('#defaultDomainIcon')).not.toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { DropdownItem, InputGroup } from 'reactstrap';
|
||||
import { DomainSelector } from '../../src/domains/DomainSelector';
|
||||
import { DomainsList } from '../../src/domains/reducers/domainsList';
|
||||
import { ShlinkDomain } from '../../src/api/types';
|
||||
import { DropdownBtn } from '../../src/utils/DropdownBtn';
|
||||
|
||||
describe('<DomainSelector />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const domainsList = Mock.of<DomainsList>({
|
||||
domains: [
|
||||
Mock.of<ShlinkDomain>({ domain: 'default.com', isDefault: true }),
|
||||
|
@ -15,51 +12,59 @@ describe('<DomainSelector />', () => {
|
|||
Mock.of<ShlinkDomain>({ domain: 'bar.com' }),
|
||||
],
|
||||
});
|
||||
const createWrapper = (value = '') => {
|
||||
wrapper = shallow(
|
||||
const setUp = (value = '') => render(
|
||||
<DomainSelector value={value} domainsList={domainsList} listDomains={jest.fn()} onChange={jest.fn()} />,
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
afterEach(() => wrapper.unmount());
|
||||
|
||||
it.each([
|
||||
['', 'Domain', 'domains-dropdown__toggle-btn'],
|
||||
['my-domain.com', 'Domain: my-domain.com', 'domains-dropdown__toggle-btn--active'],
|
||||
])('shows dropdown by default', (value, expectedText, expectedClassName) => {
|
||||
const wrapper = createWrapper(value);
|
||||
const input = wrapper.find(InputGroup);
|
||||
const dropdown = wrapper.find(DropdownBtn);
|
||||
])('shows dropdown by default', async (value, expectedText, expectedClassName) => {
|
||||
setUp(value);
|
||||
|
||||
expect(input).toHaveLength(0);
|
||||
expect(dropdown).toHaveLength(1);
|
||||
expect(dropdown.find(DropdownItem)).toHaveLength(5);
|
||||
expect(dropdown.prop('text')).toEqual(expectedText);
|
||||
expect(dropdown.prop('className')).toEqual(expectedClassName);
|
||||
const btn = screen.getByRole('button', { name: expectedText });
|
||||
|
||||
expect(screen.queryByPlaceholderText('Domain')).not.toBeInTheDocument();
|
||||
expect(btn).toHaveAttribute(
|
||||
'class',
|
||||
`dropdown-btn__toggle btn-block ${expectedClassName} dropdown-toggle btn btn-primary`,
|
||||
);
|
||||
fireEvent.click(btn);
|
||||
|
||||
await waitFor(() => expect(screen.getByRole('menu')).toBeInTheDocument());
|
||||
expect(screen.getAllByRole('menuitem')).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('allows toggling between dropdown and input', () => {
|
||||
const wrapper = createWrapper();
|
||||
it('allows toggling between dropdown and input', async () => {
|
||||
setUp();
|
||||
|
||||
wrapper.find(DropdownItem).last().simulate('click');
|
||||
expect(wrapper.find(InputGroup)).toHaveLength(1);
|
||||
expect(wrapper.find(DropdownBtn)).toHaveLength(0);
|
||||
expect(screen.queryByPlaceholderText('Domain')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Domain' })).toBeInTheDocument();
|
||||
|
||||
wrapper.find('.domains-dropdown__back-btn').simulate('click');
|
||||
expect(wrapper.find(InputGroup)).toHaveLength(0);
|
||||
expect(wrapper.find(DropdownBtn)).toHaveLength(1);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Domain' }));
|
||||
fireEvent.click(await screen.findByText('New domain'));
|
||||
|
||||
expect(screen.getByPlaceholderText('Domain')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Domain' })).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Back to domains list' }));
|
||||
|
||||
expect(screen.queryByPlaceholderText('Domain')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Domain' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[0, 'default.com<span class="float-end text-muted">default</span>'],
|
||||
[0, 'default.comdefault'],
|
||||
[1, 'foo.com'],
|
||||
[2, 'bar.com'],
|
||||
])('shows expected content on every item', (index, expectedContent) => {
|
||||
const item = createWrapper().find(DropdownItem).at(index);
|
||||
])('shows expected content on every item', async (index, expectedContent) => {
|
||||
setUp();
|
||||
|
||||
expect(item.html()).toContain(expectedContent);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Domain' }));
|
||||
const items = await screen.findAllByRole('menuitem');
|
||||
|
||||
expect(items[index]).toHaveTextContent(expectedContent);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { DomainsList } from '../../src/domains/reducers/domainsList';
|
||||
import { ManageDomains } from '../../src/domains/ManageDomains';
|
||||
import Message from '../../src/utils/Message';
|
||||
import { Result } from '../../src/utils/Result';
|
||||
import SearchField from '../../src/utils/SearchField';
|
||||
import { ProblemDetailsError, ShlinkDomain } from '../../src/api/types';
|
||||
import { ShlinkApiError } from '../../src/api/ShlinkApiError';
|
||||
import { DomainRow } from '../../src/domains/DomainRow';
|
||||
import { SelectedServer } from '../../src/servers/data';
|
||||
|
||||
describe('<ManageDomains />', () => {
|
||||
const listDomains = jest.fn();
|
||||
const filterDomains = jest.fn();
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (domainsList: DomainsList) => {
|
||||
wrapper = shallow(
|
||||
const setUp = (domainsList: DomainsList) => render(
|
||||
<ManageDomains
|
||||
listDomains={listDomains}
|
||||
filterDomains={filterDomains}
|
||||
|
@ -26,83 +19,52 @@ describe('<ManageDomains />', () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it('shows loading message while domains are loading', () => {
|
||||
const wrapper = createWrapper(Mock.of<DomainsList>({ loading: true, filteredDomains: [] }));
|
||||
const message = wrapper.find(Message);
|
||||
const searchField = wrapper.find(SearchField);
|
||||
const result = wrapper.find(Result);
|
||||
const apiError = wrapper.find(ShlinkApiError);
|
||||
setUp(Mock.of<DomainsList>({ loading: true, filteredDomains: [] }));
|
||||
|
||||
expect(message).toHaveLength(1);
|
||||
expect(message.prop('loading')).toEqual(true);
|
||||
expect(searchField).toHaveLength(0);
|
||||
expect(result).toHaveLength(0);
|
||||
expect(apiError).toHaveLength(0);
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Error loading domains :(')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows error result when domains loading fails', () => {
|
||||
const errorData = Mock.of<ProblemDetailsError>();
|
||||
const wrapper = createWrapper(Mock.of<DomainsList>(
|
||||
{ loading: false, error: true, errorData, filteredDomains: [] },
|
||||
));
|
||||
const message = wrapper.find(Message);
|
||||
const searchField = wrapper.find(SearchField);
|
||||
const result = wrapper.find(Result);
|
||||
const apiError = wrapper.find(ShlinkApiError);
|
||||
it.each([
|
||||
[undefined, 'Error loading domains :('],
|
||||
[Mock.of<ProblemDetailsError>(), 'Error loading domains :('],
|
||||
[Mock.of<ProblemDetailsError>({ detail: 'Foo error!!' }), 'Foo error!!'],
|
||||
])('shows error result when domains loading fails', (errorData, expectedErrorMessage) => {
|
||||
setUp(Mock.of<DomainsList>({ loading: false, error: true, errorData, filteredDomains: [] }));
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result.prop('type')).toEqual('error');
|
||||
expect(apiError).toHaveLength(1);
|
||||
expect(apiError.prop('errorData')).toEqual(errorData);
|
||||
expect(searchField).toHaveLength(1);
|
||||
expect(message).toHaveLength(0);
|
||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
||||
expect(screen.getByText(expectedErrorMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('filters domains when SearchField changes', () => {
|
||||
const wrapper = createWrapper(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] }));
|
||||
const searchField = wrapper.find(SearchField);
|
||||
it('filters domains when SearchField changes', async () => {
|
||||
setUp(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] }));
|
||||
|
||||
expect(filterDomains).not.toHaveBeenCalled();
|
||||
searchField.simulate('change');
|
||||
expect(filterDomains).toHaveBeenCalledTimes(1);
|
||||
fireEvent.change(screen.getByPlaceholderText('Search...'), { target: { value: 'Foo' } });
|
||||
await waitFor(() => expect(filterDomains).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
it('shows expected headers', () => {
|
||||
const wrapper = createWrapper(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] }));
|
||||
const headerCells = wrapper.find('th');
|
||||
it('shows expected headers and one row when list of domains is empty', () => {
|
||||
setUp(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] }));
|
||||
|
||||
expect(headerCells).toHaveLength(7);
|
||||
expect(screen.getAllByRole('columnheader')).toHaveLength(7);
|
||||
expect(screen.getByText('No results found')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('one row when list of domains is empty', () => {
|
||||
const wrapper = createWrapper(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] }));
|
||||
const tableBody = wrapper.find('tbody');
|
||||
const regularRows = tableBody.find('tr');
|
||||
const domainRows = tableBody.find(DomainRow);
|
||||
|
||||
expect(regularRows).toHaveLength(1);
|
||||
expect(regularRows.html()).toContain('No results found');
|
||||
expect(domainRows).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('as many DomainRows as domains are provided', () => {
|
||||
it('has many rows if multiple domains are provided', () => {
|
||||
const filteredDomains = [
|
||||
Mock.of<ShlinkDomain>({ domain: 'foo' }),
|
||||
Mock.of<ShlinkDomain>({ domain: 'bar' }),
|
||||
Mock.of<ShlinkDomain>({ domain: 'baz' }),
|
||||
];
|
||||
const wrapper = createWrapper(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains }));
|
||||
const tableBody = wrapper.find('tbody');
|
||||
const regularRows = tableBody.find('tr');
|
||||
const domainRows = tableBody.find(DomainRow);
|
||||
setUp(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains }));
|
||||
|
||||
expect(regularRows).toHaveLength(0);
|
||||
expect(domainRows).toHaveLength(filteredDomains.length);
|
||||
expect(screen.getAllByRole('row')).toHaveLength(filteredDomains.length + 1);
|
||||
expect(screen.getByText('foo')).toBeInTheDocument();
|
||||
expect(screen.getByText('bar')).toBeInTheDocument();
|
||||
expect(screen.getByText('baz')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,73 +1,30 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { UncontrolledTooltip } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { faTimes, faCheck, faCircleNotch } from '@fortawesome/free-solid-svg-icons';
|
||||
import { DomainStatus } from '../../../src/domains/data';
|
||||
import { DomainStatusIcon } from '../../../src/domains/helpers/DomainStatusIcon';
|
||||
|
||||
describe('<DomainStatusIcon />', () => {
|
||||
const matchMedia = jest.fn().mockReturnValue(Mock.of<MediaQueryList>({ matches: false }));
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (status: DomainStatus) => {
|
||||
wrapper = shallow(<DomainStatusIcon status={status} matchMedia={matchMedia} />);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
const setUp = (status: DomainStatus) => render(<DomainStatusIcon status={status} matchMedia={matchMedia} />);
|
||||
|
||||
beforeEach(jest.clearAllMocks);
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it('renders loading icon when status is "validating"', () => {
|
||||
const wrapper = createWrapper('validating');
|
||||
const tooltip = wrapper.find(UncontrolledTooltip);
|
||||
const faIcon = wrapper.find(FontAwesomeIcon);
|
||||
|
||||
expect(tooltip).toHaveLength(0);
|
||||
expect(faIcon).toHaveLength(1);
|
||||
expect(faIcon.prop('icon')).toEqual(faCircleNotch);
|
||||
expect(faIcon.prop('spin')).toEqual(true);
|
||||
it.each([
|
||||
['validating' as DomainStatus],
|
||||
['invalid' as DomainStatus],
|
||||
['valid' as DomainStatus],
|
||||
])('renders expected icon and tooltip when status is not validating', (status) => {
|
||||
const { container } = setUp(status);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
'invalid' as DomainStatus,
|
||||
faTimes,
|
||||
'Oops! There is some missing configuration, and short URLs shared with this domain will not work.',
|
||||
],
|
||||
['valid' as DomainStatus, faCheck, 'Congratulations! This domain is properly configured.'],
|
||||
])('renders expected icon and tooltip when status is not validating', (status, expectedIcon, expectedText) => {
|
||||
const wrapper = createWrapper(status);
|
||||
const tooltip = wrapper.find(UncontrolledTooltip);
|
||||
const faIcon = wrapper.find(FontAwesomeIcon);
|
||||
const getTooltipText = (): string => {
|
||||
const children = tooltip.prop('children');
|
||||
['invalid' as DomainStatus],
|
||||
['valid' as DomainStatus],
|
||||
])('renders proper tooltip based on state', async (status) => {
|
||||
const { container } = setUp(status);
|
||||
|
||||
if (typeof children === 'string') {
|
||||
return children;
|
||||
}
|
||||
|
||||
return tooltip.find('span').html();
|
||||
};
|
||||
|
||||
expect(tooltip).toHaveLength(1);
|
||||
expect(tooltip.prop('autohide')).toEqual(status === 'valid');
|
||||
expect(getTooltipText()).toContain(expectedText);
|
||||
expect(faIcon).toHaveLength(1);
|
||||
expect(faIcon.prop('icon')).toEqual(expectedIcon);
|
||||
expect(faIcon.prop('spin')).toEqual(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[true, 'top-start'],
|
||||
[false, 'left'],
|
||||
])('places the tooltip properly based on query match', (isMobile, expectedPlacement) => {
|
||||
matchMedia.mockReturnValue(Mock.of<MediaQueryList>({ matches: isMobile }));
|
||||
|
||||
const wrapper = createWrapper('valid');
|
||||
const tooltip = wrapper.find(UncontrolledTooltip);
|
||||
|
||||
expect(tooltip).toHaveLength(1);
|
||||
expect(tooltip.prop('placement')).toEqual(expectedPlacement);
|
||||
container.firstChild && fireEvent.mouseOver(container.firstChild);
|
||||
expect(await screen.findByRole('tooltip')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is not validating 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="svg-inline--fa fa-circle-notch fa-spin fa-fw "
|
||||
data-icon="circle-notch"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M222.7 32.15C227.7 49.08 218.1 66.9 201.1 71.94C121.8 95.55 64 169.1 64 255.1C64 362 149.1 447.1 256 447.1C362 447.1 448 362 448 255.1C448 169.1 390.2 95.55 310.9 71.94C293.9 66.9 284.3 49.08 289.3 32.15C294.4 15.21 312.2 5.562 329.1 10.6C434.9 42.07 512 139.1 512 255.1C512 397.4 397.4 511.1 256 511.1C114.6 511.1 0 397.4 0 255.1C0 139.1 77.15 42.07 182.9 10.6C199.8 5.562 217.6 15.21 222.7 32.15V32.15z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is not validating 2`] = `
|
||||
<span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="svg-inline--fa fa-xmark fa-fw text-danger"
|
||||
data-icon="xmark"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 320 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is not validating 3`] = `
|
||||
<span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="svg-inline--fa fa-check fa-fw text-muted"
|
||||
data-icon="check"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 448 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M438.6 105.4C451.1 117.9 451.1 138.1 438.6 150.6L182.6 406.6C170.1 419.1 149.9 419.1 137.4 406.6L9.372 278.6C-3.124 266.1-3.124 245.9 9.372 233.4C21.87 220.9 42.13 220.9 54.63 233.4L159.1 338.7L393.4 105.4C405.9 92.88 426.1 92.88 438.6 105.4H438.6z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`<DomainStatusIcon /> renders proper tooltip based on state 1`] = `
|
||||
<div
|
||||
class="tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
<span>
|
||||
Oops! There is some missing configuration, and short URLs shared with this domain will not work.
|
||||
<br />
|
||||
Check the
|
||||
<a
|
||||
href="https://slnk.to/multi-domain-docs"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
documentation
|
||||
</a>
|
||||
in order to find out what is missing.
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<DomainStatusIcon /> renders proper tooltip based on state 2`] = `
|
||||
<div
|
||||
class="tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
Congratulations! This domain is properly configured.
|
||||
</div>
|
||||
`;
|
|
@ -4,7 +4,7 @@ import { useLocation } from 'react-router-dom';
|
|||
import { TagsTable as createTagsTable } from '../../src/tags/TagsTable';
|
||||
import { SelectedServer } from '../../src/servers/data';
|
||||
import { rangeOf } from '../../src/utils/utils';
|
||||
import SimplePaginator from '../../src/common/SimplePaginator';
|
||||
import { SimplePaginator } from '../../src/common/SimplePaginator';
|
||||
import { NormalizedTag } from '../../src/tags/data';
|
||||
|
||||
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useLocation: jest.fn() }));
|
||||
|
|
|
@ -2,7 +2,7 @@ import { shallow, ShallowWrapper } from 'enzyme';
|
|||
import { Mock } from 'ts-mockery';
|
||||
import VisitsTable, { VisitsTableProps } from '../../src/visits/VisitsTable';
|
||||
import { rangeOf } from '../../src/utils/utils';
|
||||
import SimplePaginator from '../../src/common/SimplePaginator';
|
||||
import { SimplePaginator } from '../../src/common/SimplePaginator';
|
||||
import SearchField from '../../src/utils/SearchField';
|
||||
import { NormalizedVisit } from '../../src/visits/types';
|
||||
import { ReachableServer, SelectedServer } from '../../src/servers/data';
|
||||
|
|
|
@ -1,58 +1,20 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import { prettify } from '../../../src/utils/helpers/numbers';
|
||||
import { MAIN_COLOR, MAIN_COLOR_ALPHA } from '../../../src/utils/theme';
|
||||
import { HorizontalBarChart } from '../../../src/visits/charts/HorizontalBarChart';
|
||||
import { render } from '@testing-library/react';
|
||||
import { HorizontalBarChart, HorizontalBarChartProps } from '../../../src/visits/charts/HorizontalBarChart';
|
||||
|
||||
describe('<HorizontalBarChart />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const stats = {
|
||||
foo: 123,
|
||||
bar: 456,
|
||||
const setUp = (props: HorizontalBarChartProps) => {
|
||||
const { container } = render(<HorizontalBarChart {...props} />);
|
||||
return container.querySelector('canvas')?.getContext('2d')?.__getEvents(); // eslint-disable-line no-underscore-dangle
|
||||
};
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it('renders Bar with expected properties', () => {
|
||||
wrapper = shallow(<HorizontalBarChart stats={stats} />);
|
||||
const horizontal = wrapper.find(Bar);
|
||||
|
||||
expect(horizontal).toHaveLength(1);
|
||||
|
||||
const { datasets: [{ backgroundColor, borderColor }] } = horizontal.prop('data') as any;
|
||||
const { plugins, scales } = (horizontal.prop('options') ?? {}) as any;
|
||||
|
||||
expect(backgroundColor).toEqual(MAIN_COLOR_ALPHA);
|
||||
expect(borderColor).toEqual(MAIN_COLOR);
|
||||
expect(plugins.legend).toEqual({ display: false });
|
||||
expect(scales).toEqual({
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
stacked: true,
|
||||
ticks: {
|
||||
precision: 0,
|
||||
callback: prettify,
|
||||
},
|
||||
},
|
||||
y: { stacked: true },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ foo: 23 }, [100, 456], [23, 0]],
|
||||
[{ foo: 50 }, [73, 456], [50, 0]],
|
||||
[{ bar: 45 }, [123, 411], [0, 45]],
|
||||
[{ bar: 20, foo: 13 }, [110, 436], [13, 20]],
|
||||
[undefined, [123, 456], undefined],
|
||||
])('splits highlighted data from regular data', (highlightedStats, expectedData, expectedHighlightedData) => {
|
||||
wrapper = shallow(<HorizontalBarChart stats={stats} highlightedStats={highlightedStats} />);
|
||||
const horizontal = wrapper.find(Bar);
|
||||
[{ foo: 123, bar: 456 }, undefined],
|
||||
[{ one: 999, two: 131313 }, { one: 30, two: 100 }],
|
||||
[{ one: 999, two: 131313, max: 3 }, { one: 30, two: 100 }],
|
||||
])('renders chart with expected canvas', (stats, highlightedStats) => {
|
||||
const events = setUp({ stats, highlightedStats });
|
||||
|
||||
const { datasets: [{ data, label }, highlightedData] } = horizontal.prop('data') as any;
|
||||
|
||||
expect(label).toEqual('Visits');
|
||||
expect(data).toEqual(expectedData);
|
||||
expectedHighlightedData && expect(highlightedData.data).toEqual(expectedHighlightedData);
|
||||
!expectedHighlightedData && expect(highlightedData).toBeUndefined();
|
||||
expect(events).toBeTruthy();
|
||||
expect(events).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,521 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<HorizontalBarChart /> renders chart with expected canvas 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"props": Object {
|
||||
"a": 1,
|
||||
"b": 0,
|
||||
"c": 0,
|
||||
"d": 1,
|
||||
"e": 0,
|
||||
"f": 0,
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "setTransform",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "foo",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "bar",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "0",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "500",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`<HorizontalBarChart /> renders chart with expected canvas 2`] = `
|
||||
Array [
|
||||
Object {
|
||||
"props": Object {
|
||||
"a": 1,
|
||||
"b": 0,
|
||||
"c": 0,
|
||||
"d": 1,
|
||||
"e": 0,
|
||||
"f": 0,
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "setTransform",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "one",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "two",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "0",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "200,000",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`<HorizontalBarChart /> renders chart with expected canvas 3`] = `
|
||||
Array [
|
||||
Object {
|
||||
"props": Object {
|
||||
"a": 1,
|
||||
"b": 0,
|
||||
"c": 0,
|
||||
"d": 1,
|
||||
"e": 0,
|
||||
"f": 0,
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "setTransform",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "one",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "two",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "max",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "0",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"text": "200,000",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "measureText",
|
||||
},
|
||||
Object {
|
||||
"props": Object {
|
||||
"value": "12px \\"Helvetica Neue\\", 'Helvetica', 'Arial', sans-serif",
|
||||
},
|
||||
"transform": Array [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "font",
|
||||
},
|
||||
]
|
||||
`;
|
Loading…
Reference in a new issue