mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 01:20:24 +03:00
Merge pull request #639 from acelaya-forks/feature/more-rtl-tests
Feature/more rtl tests
This commit is contained in:
commit
116c36febc
20 changed files with 832 additions and 884 deletions
1157
package-lock.json
generated
1157
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -20,7 +20,7 @@
|
|||
"test:coverage": "npm run test -- --coverage --coverageReporters=text --coverageReporters=text-summary",
|
||||
"test:ci": "npm run test:coverage -- --coverageReporters=clover",
|
||||
"test:pretty": "npm run test:coverage -- --coverageReporters=html",
|
||||
"mutate": "./node_modules/.bin/stryker run --concurrency 4"
|
||||
"mutate": "./node_modules/.bin/stryker run --concurrency 4 --ignoreStatic"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||
|
@ -68,9 +68,9 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@shlinkio/eslint-config-js-coding-standard": "~2.0.0",
|
||||
"@stryker-mutator/core": "^5.6.1",
|
||||
"@stryker-mutator/jest-runner": "^5.6.1",
|
||||
"@stryker-mutator/typescript-checker": "^5.6.1",
|
||||
"@stryker-mutator/core": "^6.0.2",
|
||||
"@stryker-mutator/jest-runner": "^6.0.2",
|
||||
"@stryker-mutator/typescript-checker": "^6.0.2",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.1.1",
|
||||
"@types/classnames": "^2.3.1",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, FC } from 'react';
|
||||
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import NotFound from '../common/NotFound';
|
||||
import { NotFound } from '../common/NotFound';
|
||||
import { ServersMap } from '../servers/data';
|
||||
import { Settings } from '../settings/reducers/settings';
|
||||
import { changeThemeInMarkup } from '../utils/theme';
|
||||
|
@ -17,7 +17,7 @@ interface AppProps {
|
|||
appUpdated: boolean;
|
||||
}
|
||||
|
||||
const App = (
|
||||
export const App = (
|
||||
MainHeader: FC,
|
||||
Home: FC,
|
||||
MenuLayout: FC,
|
||||
|
@ -65,5 +65,3 @@ const App = (
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Bottle from 'bottlejs';
|
||||
import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates';
|
||||
import App from '../App';
|
||||
import { App } from '../App';
|
||||
import { ConnectDecorator } from '../../container/types';
|
||||
|
||||
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
|
|
|
@ -24,7 +24,7 @@ export const AppUpdateBanner: FC<AppUpdateBannerProps> = ({ isOpen, toggle, forc
|
|||
<h4 className="mb-4">This app has just been updated!</h4>
|
||||
<p className="mb-0">
|
||||
Restart it to enjoy the new features.
|
||||
<Button disabled={isUpdating} className="ms-2" color="secondary" size="sm" onClick={update}>
|
||||
<Button role="button" disabled={isUpdating} className="ms-2" color="secondary" size="sm" onClick={update}>
|
||||
{!isUpdating && <>Restart now <FontAwesomeIcon icon={reloadIcon} className="ms-1" /></>}
|
||||
{isUpdating && <>Restarting...</>}
|
||||
</Button>
|
||||
|
|
|
@ -6,10 +6,10 @@ interface ErrorHandlerState {
|
|||
hasError: boolean;
|
||||
}
|
||||
|
||||
const ErrorHandlerCreator = (
|
||||
export const ErrorHandler = (
|
||||
{ location }: Window,
|
||||
{ error }: Console,
|
||||
) => class ErrorHandler extends Component<any, ErrorHandlerState> {
|
||||
) => class extends Component<any, ErrorHandlerState> {
|
||||
public constructor(props: object) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
|
@ -44,5 +44,3 @@ const ErrorHandlerCreator = (
|
|||
return children;
|
||||
}
|
||||
};
|
||||
|
||||
export default ErrorHandlerCreator;
|
||||
|
|
|
@ -10,11 +10,11 @@ import { ServersMap } from '../servers/data';
|
|||
import { ShlinkLogo } from './img/ShlinkLogo';
|
||||
import './Home.scss';
|
||||
|
||||
export interface HomeProps {
|
||||
interface HomeProps {
|
||||
servers: ServersMap;
|
||||
}
|
||||
|
||||
const Home = ({ servers }: HomeProps) => {
|
||||
export const Home = ({ servers }: HomeProps) => {
|
||||
const navigate = useNavigate();
|
||||
const serversList = values(servers);
|
||||
const hasServers = !isEmpty(serversList);
|
||||
|
@ -65,5 +65,3 @@ const Home = ({ servers }: HomeProps) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useToggle } from '../utils/helpers/hooks';
|
|||
import { ShlinkLogo } from './img/ShlinkLogo';
|
||||
import './MainHeader.scss';
|
||||
|
||||
const MainHeader = (ServersDropdown: FC) => () => {
|
||||
export const MainHeader = (ServersDropdown: FC) => () => {
|
||||
const [isOpen, toggleOpen, , close] = useToggle();
|
||||
const location = useLocation();
|
||||
const { pathname } = location;
|
||||
|
@ -41,5 +41,3 @@ const MainHeader = (ServersDropdown: FC) => () => {
|
|||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainHeader;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { withSelectedServer } from '../servers/helpers/withSelectedServer';
|
|||
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
|
||||
import { supportsDomainRedirects, supportsDomainVisits, supportsNonOrphanVisits } from '../utils/helpers/features';
|
||||
import { isReachableServer } from '../servers/data';
|
||||
import NotFound from './NotFound';
|
||||
import { NotFound } from './NotFound';
|
||||
import { AsideMenuProps } from './AsideMenu';
|
||||
import './MenuLayout.scss';
|
||||
|
||||
|
@ -16,7 +16,7 @@ interface MenuLayoutProps {
|
|||
sidebarNotPresent: Function;
|
||||
}
|
||||
|
||||
const MenuLayout = (
|
||||
export const MenuLayout = (
|
||||
TagsList: FC,
|
||||
ShortUrlsList: FC,
|
||||
AsideMenu: FC<AsideMenuProps>,
|
||||
|
@ -86,5 +86,3 @@ const MenuLayout = (
|
|||
</>
|
||||
);
|
||||
}, ServerError);
|
||||
|
||||
export default MenuLayout;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { SimpleCard } from '../utils/SimpleCard';
|
|||
|
||||
type NotFoundProps = PropsWithChildren<{ to?: string }>;
|
||||
|
||||
const NotFound: FC<NotFoundProps> = ({ to = '/', children = 'Home' }) => (
|
||||
export const NotFound: FC<NotFoundProps> = ({ to = '/', children = 'Home' }) => (
|
||||
<div className="home">
|
||||
<SimpleCard className="p-4">
|
||||
<h2>Oops! We could not find requested route.</h2>
|
||||
|
@ -17,5 +17,3 @@ const NotFound: FC<NotFoundProps> = ({ to = '/', children = 'Home' }) => (
|
|||
</SimpleCard>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default NotFound;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import axios from 'axios';
|
||||
import Bottle from 'bottlejs';
|
||||
import ScrollToTop from '../ScrollToTop';
|
||||
import MainHeader from '../MainHeader';
|
||||
import Home from '../Home';
|
||||
import MenuLayout from '../MenuLayout';
|
||||
import { MainHeader } from '../MainHeader';
|
||||
import { Home } from '../Home';
|
||||
import { MenuLayout } from '../MenuLayout';
|
||||
import AsideMenu from '../AsideMenu';
|
||||
import ErrorHandler from '../ErrorHandler';
|
||||
import { ErrorHandler } from '../ErrorHandler';
|
||||
import ShlinkVersionsContainer from '../ShlinkVersionsContainer';
|
||||
import { ConnectDecorator } from '../../container/types';
|
||||
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
|
||||
|
|
|
@ -1,71 +1,61 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Route, useLocation } from 'react-router-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { Settings } from '../../src/settings/reducers/settings';
|
||||
import appFactory from '../../src/app/App';
|
||||
import { AppUpdateBanner } from '../../src/common/AppUpdateBanner';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn().mockReturnValue({}),
|
||||
}));
|
||||
import { App as createApp } from '../../src/app/App';
|
||||
|
||||
describe('<App />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
const MainHeader = () => null;
|
||||
const ShlinkVersions = () => null;
|
||||
const App = appFactory(
|
||||
MainHeader,
|
||||
() => null,
|
||||
() => null,
|
||||
() => null,
|
||||
() => null,
|
||||
() => null,
|
||||
() => null,
|
||||
ShlinkVersions,
|
||||
const App = createApp(
|
||||
() => <>MainHeader</>,
|
||||
() => <>Home</>,
|
||||
() => <>MenuLayout</>,
|
||||
() => <>CreateServer</>,
|
||||
() => <>EditServer</>,
|
||||
() => <>SettingsComp</>,
|
||||
() => <>ManageServers</>,
|
||||
() => <>ShlinkVersions</>,
|
||||
);
|
||||
const createWrapper = () => {
|
||||
wrapper = shallow(
|
||||
const setUp = (activeRoute = '/') => {
|
||||
const history = createMemoryHistory();
|
||||
history.push(activeRoute);
|
||||
|
||||
return render(
|
||||
<Router location={history.location} navigator={history}>
|
||||
<App
|
||||
fetchServers={() => {}}
|
||||
servers={{}}
|
||||
settings={Mock.all<Settings>()}
|
||||
appUpdated={false}
|
||||
appUpdated
|
||||
resetAppUpdate={() => {}}
|
||||
/>,
|
||||
/>
|
||||
</Router>,
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it('renders children components', () => {
|
||||
const wrapper = createWrapper();
|
||||
setUp();
|
||||
|
||||
expect(wrapper.find(MainHeader)).toHaveLength(1);
|
||||
expect(wrapper.find(ShlinkVersions)).toHaveLength(1);
|
||||
expect(wrapper.find(AppUpdateBanner)).toHaveLength(1);
|
||||
expect(screen.getByText('MainHeader')).toBeInTheDocument();
|
||||
expect(screen.getByText('ShlinkVersions')).toBeInTheDocument();
|
||||
expect(screen.getByText('This app has just been updated!')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders app main routes', () => {
|
||||
const wrapper = createWrapper();
|
||||
const routes = wrapper.find(Route);
|
||||
const expectedPaths = [
|
||||
undefined,
|
||||
'/settings/*',
|
||||
'/manage-servers',
|
||||
'/server/create',
|
||||
'/server/:serverId/edit',
|
||||
'/server/:serverId/*',
|
||||
];
|
||||
|
||||
expect.assertions(expectedPaths.length + 1);
|
||||
expect(routes).toHaveLength(expectedPaths.length + 1);
|
||||
expectedPaths.forEach((path, index) => {
|
||||
expect(routes.at(index).prop('path')).toEqual(path);
|
||||
});
|
||||
it.each([
|
||||
['/settings/foo', 'SettingsComp'],
|
||||
['/settings/bar', 'SettingsComp'],
|
||||
['/manage-servers', 'ManageServers'],
|
||||
['/server/create', 'CreateServer'],
|
||||
['/server/abc123/edit', 'EditServer'],
|
||||
['/server/def456/edit', 'EditServer'],
|
||||
['/server/abc123/foo', 'MenuLayout'],
|
||||
['/server/def456/bar', 'MenuLayout'],
|
||||
['/other', 'Oops! We could not find requested route.'],
|
||||
])('renders expected route', async (activeRoute, expectedComponent) => {
|
||||
setUp(activeRoute);
|
||||
expect(await screen.findByText(expectedComponent)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
@ -73,11 +63,9 @@ describe('<App />', () => {
|
|||
['/bar', 'shlink-wrapper'],
|
||||
['/', 'shlink-wrapper d-flex d-md-block align-items-center'],
|
||||
])('renders expected classes on shlink-wrapper based on current pathname', (pathname, expectedClasses) => {
|
||||
(useLocation as any).mockReturnValue({ pathname });
|
||||
const { container } = setUp(pathname);
|
||||
const shlinkWrapper = container.querySelector('.shlink-wrapper');
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const shlinkWrapper = wrapper.find('.shlink-wrapper');
|
||||
|
||||
expect(shlinkWrapper.prop('className')).toEqual(expectedClasses);
|
||||
expect(shlinkWrapper).toHaveAttribute('class', expectedClasses);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,43 +1,31 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Button } from 'reactstrap';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { AppUpdateBanner } from '../../src/common/AppUpdateBanner';
|
||||
import { SimpleCard } from '../../src/utils/SimpleCard';
|
||||
|
||||
describe('<AppUpdateBanner />', () => {
|
||||
const toggle = jest.fn();
|
||||
const forceUpdate = jest.fn();
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<AppUpdateBanner isOpen toggle={toggle} forceUpdate={forceUpdate} />);
|
||||
});
|
||||
beforeEach(() => render(<AppUpdateBanner isOpen toggle={toggle} forceUpdate={forceUpdate} />));
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it('renders an alert with expected props', () => {
|
||||
expect(wrapper.prop('className')).toEqual('app-update-banner');
|
||||
expect(wrapper.prop('isOpen')).toEqual(true);
|
||||
expect(wrapper.prop('toggle')).toEqual(toggle);
|
||||
expect(wrapper.prop('tag')).toEqual(SimpleCard);
|
||||
expect(wrapper.prop('color')).toEqual('secondary');
|
||||
it('renders initial state', () => {
|
||||
expect(screen.getByRole('heading')).toHaveTextContent('This app has just been updated!');
|
||||
expect(screen.queryByText('Restarting...')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Restart now')).not.toHaveAttribute('disabled');
|
||||
});
|
||||
|
||||
it('invokes toggle when alert is toggled', () => {
|
||||
(wrapper.prop('toggle') as Function)();
|
||||
|
||||
it('invokes toggle when alert is closed', () => {
|
||||
expect(toggle).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByLabelText('Close'));
|
||||
expect(toggle).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('triggers the update when clicking the button', () => {
|
||||
expect(wrapper.find(Button).html()).toContain('Restart now');
|
||||
expect(wrapper.find(Button).prop('disabled')).toEqual(false);
|
||||
it('triggers the update when clicking the button', async () => {
|
||||
expect(forceUpdate).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.find(Button).simulate('click');
|
||||
|
||||
expect(wrapper.find(Button).html()).toContain('Restarting...');
|
||||
expect(wrapper.find(Button).prop('disabled')).toEqual(true);
|
||||
fireEvent.click(screen.getByText(/^Restart now/));
|
||||
expect(forceUpdate).toHaveBeenCalled();
|
||||
expect(await screen.findByText('Restarting...')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/^Restart now/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,32 +1,41 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import asideMenuCreator from '../../src/common/AsideMenu';
|
||||
import { ReachableServer } from '../../src/servers/data';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn().mockReturnValue({ pathname: '' }),
|
||||
}));
|
||||
import { SemVer } from '../../src/utils/helpers/version';
|
||||
|
||||
describe('<AsideMenu />', () => {
|
||||
let wrapped: ShallowWrapper;
|
||||
const DeleteServerButton = () => null;
|
||||
const AsideMenu = asideMenuCreator(() => <>DeleteServerButton</>);
|
||||
const setUp = (version: SemVer, id: string | false = 'abc123') => render(
|
||||
<MemoryRouter>
|
||||
<AsideMenu selectedServer={Mock.of<ReachableServer>({ id: id || undefined, version })} />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
const AsideMenu = asideMenuCreator(DeleteServerButton);
|
||||
it.each([
|
||||
['2.7.0' as SemVer, 5],
|
||||
['2.8.0' as SemVer, 6],
|
||||
])('contains links to different sections', (version, expectedAmountOfLinks) => {
|
||||
setUp(version);
|
||||
|
||||
wrapped = shallow(<AsideMenu selectedServer={Mock.of<ReachableServer>({ id: 'abc123' })} />);
|
||||
});
|
||||
afterEach(() => wrapped.unmount());
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
it('contains links to different sections', () => {
|
||||
const links = wrapped.find('[to]');
|
||||
|
||||
expect(links).toHaveLength(5);
|
||||
links.forEach((link) => expect(link.prop('to')).toContain('abc123'));
|
||||
expect.assertions(links.length + 1);
|
||||
expect(links).toHaveLength(expectedAmountOfLinks);
|
||||
links.forEach((link) => expect(link.getAttribute('href')).toContain('abc123'));
|
||||
});
|
||||
|
||||
it('contains a button to delete server', () => {
|
||||
expect(wrapped.find(DeleteServerButton)).toHaveLength(1);
|
||||
it.each([
|
||||
['abc', true],
|
||||
[false, false],
|
||||
])('contains a button to delete server if appropriate', (id, shouldHaveBtn) => {
|
||||
setUp('2.8.0', id as string | false);
|
||||
|
||||
if (shouldHaveBtn) {
|
||||
expect(screen.getByText('DeleteServerButton')).toBeInTheDocument();
|
||||
} else {
|
||||
expect(screen.queryByText('DeleteServerButton')).not.toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,38 +1,44 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Button } from 'reactstrap';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import createErrorHandler from '../../src/common/ErrorHandler';
|
||||
import { SimpleCard } from '../../src/utils/SimpleCard';
|
||||
import { ErrorHandler as createErrorHandler } from '../../src/common/ErrorHandler';
|
||||
|
||||
const ComponentWithError = () => {
|
||||
throw new Error('Error!!');
|
||||
};
|
||||
|
||||
describe('<ErrorHandler />', () => {
|
||||
const reload = jest.fn();
|
||||
const window = Mock.of<Window>({
|
||||
location: {
|
||||
reload: jest.fn(),
|
||||
},
|
||||
location: { reload },
|
||||
});
|
||||
const console = Mock.of<Console>({ error: jest.fn() });
|
||||
let wrapper: ShallowWrapper;
|
||||
const cons = Mock.of<Console>({ error: jest.fn() });
|
||||
const ErrorHandler = createErrorHandler(window, cons);
|
||||
|
||||
beforeEach(() => {
|
||||
const ErrorHandler = createErrorHandler(window, console);
|
||||
|
||||
wrapper = shallow(<ErrorHandler children={<span>Foo</span>} />);
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {}); // Silence react errors
|
||||
});
|
||||
|
||||
afterEach(() => wrapper.unmount());
|
||||
afterEach(jest.resetAllMocks);
|
||||
|
||||
it('renders children when no error has occurred', () => {
|
||||
expect(wrapper.text()).toEqual('Foo');
|
||||
expect(wrapper.find(Button)).toHaveLength(0);
|
||||
render(<ErrorHandler children={<span>Foo</span>} />);
|
||||
|
||||
expect(screen.getByText('Foo')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Oops! This is awkward :S')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders error page when error has occurred', () => {
|
||||
wrapper.setState({ hasError: true });
|
||||
render(<ErrorHandler children={<ComponentWithError />} />);
|
||||
|
||||
expect(wrapper.find(SimpleCard).contains('Oops! This is awkward :S')).toEqual(true);
|
||||
expect(wrapper.find(SimpleCard).contains(
|
||||
'It seems that something went wrong. Try refreshing the page or just click this button.',
|
||||
)).toEqual(true);
|
||||
expect(wrapper.find(Button)).toHaveLength(1);
|
||||
expect(screen.getByText('Oops! This is awkward :S')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('reloads page on button click', () => {
|
||||
render(<ErrorHandler children={<ComponentWithError />} />);
|
||||
|
||||
expect(reload).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button'));
|
||||
expect(reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,31 +1,19 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import Home, { HomeProps } from '../../src/common/Home';
|
||||
import { ServerWithId } from '../../src/servers/data';
|
||||
import { ShlinkLogo } from '../../src/common/img/ShlinkLogo';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: jest.fn().mockReturnValue(jest.fn()),
|
||||
}));
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Home } from '../../src/common/Home';
|
||||
import { ServersMap, ServerWithId } from '../../src/servers/data';
|
||||
|
||||
describe('<Home />', () => {
|
||||
let wrapped: ShallowWrapper;
|
||||
const createComponent = (props: Partial<HomeProps> = {}) => {
|
||||
const actualProps = { resetSelectedServer: jest.fn(), servers: {}, ...props };
|
||||
const setUp = (servers: ServersMap = {}) => render(
|
||||
<MemoryRouter>
|
||||
<Home servers={servers} />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
wrapped = shallow(<Home {...actualProps} />);
|
||||
|
||||
return wrapped;
|
||||
};
|
||||
|
||||
afterEach(() => wrapped?.unmount());
|
||||
|
||||
it('renders logo and title', () => {
|
||||
const wrapped = createComponent();
|
||||
|
||||
expect(wrapped.find(ShlinkLogo)).toHaveLength(1);
|
||||
expect(wrapped.find('.home__title')).toHaveLength(1);
|
||||
it('renders title', () => {
|
||||
setUp();
|
||||
expect(screen.getByRole('heading', { name: 'Welcome!' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
@ -33,14 +21,20 @@ describe('<Home />', () => {
|
|||
{
|
||||
'1a': Mock.of<ServerWithId>({ name: 'foo', id: '1' }),
|
||||
'2b': Mock.of<ServerWithId>({ name: 'bar', id: '2' }),
|
||||
'3c': Mock.of<ServerWithId>({ name: 'baz', id: '3' }),
|
||||
},
|
||||
0,
|
||||
3,
|
||||
],
|
||||
[{}, 3],
|
||||
])('shows link to create or set-up server only when no servers exist', (servers, expectedParagraphs) => {
|
||||
const wrapped = createComponent({ servers });
|
||||
const p = wrapped.find('p');
|
||||
[{}, 2],
|
||||
])('shows link to create or set-up server only when no servers exist', (servers, expectedServers) => {
|
||||
setUp(servers);
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
expect(p).toHaveLength(expectedParagraphs);
|
||||
expect(links).toHaveLength(expectedServers);
|
||||
|
||||
if (Object.keys(servers).length === 0) {
|
||||
expect(screen.getByText('This application will help you manage your Shlink servers.')).toBeInTheDocument();
|
||||
expect(screen.getByText('Learn more about Shlink')).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,34 +1,24 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Collapse, NavbarToggler, NavLink } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import createMainHeader from '../../src/common/MainHeader';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn().mockReturnValue({}),
|
||||
}));
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { MainHeader as createMainHeader } from '../../src/common/MainHeader';
|
||||
|
||||
describe('<MainHeader />', () => {
|
||||
const ServersDropdown = () => null;
|
||||
const MainHeader = createMainHeader(ServersDropdown);
|
||||
let wrapper: ShallowWrapper;
|
||||
const MainHeader = createMainHeader(() => <>ServersDropdown</>);
|
||||
const setUp = (pathname = '') => {
|
||||
const history = createMemoryHistory();
|
||||
history.push(pathname);
|
||||
|
||||
const createWrapper = (pathname = '') => {
|
||||
(useLocation as any).mockReturnValue({ pathname });
|
||||
|
||||
wrapper = shallow(<MainHeader />);
|
||||
|
||||
return wrapper;
|
||||
return render(
|
||||
<Router location={history.location} navigator={history}>
|
||||
<MainHeader />
|
||||
</Router>,
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it('renders ServersDropdown', () => {
|
||||
const wrapper = createWrapper();
|
||||
|
||||
expect(wrapper.find(ServersDropdown)).toHaveLength(1);
|
||||
setUp();
|
||||
expect(screen.getByText('ServersDropdown')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
@ -38,31 +28,38 @@ describe('<MainHeader />', () => {
|
|||
['/settings/foo', true],
|
||||
['/settings/bar', true],
|
||||
])('sets link to settings as active only when current path is settings', (currentPath, isActive) => {
|
||||
const wrapper = createWrapper(currentPath);
|
||||
const settingsLink = wrapper.find(NavLink);
|
||||
setUp(currentPath);
|
||||
|
||||
expect(settingsLink.prop('active')).toEqual(isActive);
|
||||
if (isActive) {
|
||||
expect(screen.getByText(/Settings$/).getAttribute('class')).toContain('active');
|
||||
} else {
|
||||
expect(screen.getByText(/Settings$/).getAttribute('class')).not.toContain('active');
|
||||
}
|
||||
});
|
||||
|
||||
it('renders expected class based on the nav bar state', () => {
|
||||
const wrapper = createWrapper();
|
||||
setUp();
|
||||
|
||||
expect(wrapper.find(NavbarToggler).find(FontAwesomeIcon).prop('className')).toEqual('main-header__toggle-icon');
|
||||
wrapper.find(NavbarToggler).simulate('click');
|
||||
expect(wrapper.find(NavbarToggler).find(FontAwesomeIcon).prop('className')).toEqual(
|
||||
'main-header__toggle-icon main-header__toggle-icon--opened',
|
||||
const toggle = screen.getByLabelText('Toggle navigation');
|
||||
const icon = toggle.firstChild;
|
||||
|
||||
expect(icon).toHaveAttribute('class', expect.stringMatching(/main-header__toggle-icon$/));
|
||||
fireEvent.click(toggle);
|
||||
expect(icon).toHaveAttribute(
|
||||
'class',
|
||||
expect.stringMatching(/main-header__toggle-icon main-header__toggle-icon--opened$/),
|
||||
);
|
||||
wrapper.find(NavbarToggler).simulate('click');
|
||||
expect(wrapper.find(NavbarToggler).find(FontAwesomeIcon).prop('className')).toEqual('main-header__toggle-icon');
|
||||
fireEvent.click(toggle);
|
||||
expect(icon).toHaveAttribute('class', expect.stringMatching(/main-header__toggle-icon$/));
|
||||
});
|
||||
|
||||
it('opens Collapse when clicking toggle', () => {
|
||||
const wrapper = createWrapper();
|
||||
it('opens Collapse when clicking toggle', async () => {
|
||||
const { container } = setUp();
|
||||
const collapse = container.querySelector('.collapse');
|
||||
const toggle = screen.getByLabelText('Toggle navigation');
|
||||
|
||||
expect(wrapper.find(Collapse).prop('isOpen')).toEqual(false);
|
||||
wrapper.find(NavbarToggler).simulate('click');
|
||||
expect(wrapper.find(Collapse).prop('isOpen')).toEqual(true);
|
||||
wrapper.find(NavbarToggler).simulate('click');
|
||||
expect(wrapper.find(Collapse).prop('isOpen')).toEqual(false);
|
||||
expect(collapse).not.toHaveAttribute('class', expect.stringContaining('show'));
|
||||
fireEvent.click(toggle);
|
||||
await waitFor(() => expect(collapse).toHaveAttribute('class', expect.stringContaining('show')));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,70 +1,89 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Route, useParams } from 'react-router-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Router, useParams } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import createMenuLayout from '../../src/common/MenuLayout';
|
||||
import { MenuLayout as createMenuLayout } from '../../src/common/MenuLayout';
|
||||
import { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../../src/servers/data';
|
||||
import { NoMenuLayout } from '../../src/common/NoMenuLayout';
|
||||
import { SemVer } from '../../src/utils/helpers/version';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: jest.fn().mockReturnValue({}),
|
||||
useLocation: jest.fn().mockReturnValue({}),
|
||||
}));
|
||||
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: jest.fn() }));
|
||||
|
||||
describe('<MenuLayout />', () => {
|
||||
const ServerError = jest.fn();
|
||||
const C = jest.fn();
|
||||
const MenuLayout = createMenuLayout(C, C, C, C, C, C, C, C, C, ServerError, C, C, C);
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (selectedServer: SelectedServer) => {
|
||||
(useParams as any).mockReturnValue({ serverId: 'abc123' });
|
||||
const MenuLayout = createMenuLayout(
|
||||
() => <>TagsList</>,
|
||||
() => <>ShortUrlsList</>,
|
||||
() => <>AsideMenu</>,
|
||||
() => <>CreateShortUrl</>,
|
||||
() => <>ShortUrlVisits</>,
|
||||
() => <>TagVisits</>,
|
||||
() => <>DomainVisits</>,
|
||||
() => <>OrphanVisits</>,
|
||||
() => <>NonOrphanVisits</>,
|
||||
() => <>ServerError</>,
|
||||
() => <>Overview</>,
|
||||
() => <>EditShortUrl</>,
|
||||
() => <>ManageDomains</>,
|
||||
);
|
||||
const setUp = (selectedServer: SelectedServer, currentPath = '/') => {
|
||||
const history = createMemoryHistory();
|
||||
history.push(currentPath);
|
||||
|
||||
wrapper = shallow(
|
||||
return render(
|
||||
<Router location={history.location} navigator={history}>
|
||||
<MenuLayout
|
||||
sidebarNotPresent={jest.fn()}
|
||||
sidebarPresent={jest.fn()}
|
||||
selectServer={jest.fn()}
|
||||
selectedServer={selectedServer}
|
||||
/>,
|
||||
/>
|
||||
</Router>,
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(useParams as any).mockReturnValue({ serverId: 'abc123' });
|
||||
});
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
||||
it.each([
|
||||
[null, NoMenuLayout],
|
||||
[Mock.of<NotFoundServer>({ serverNotFound: true }), ServerError],
|
||||
])('returns error when server is not found', (selectedServer, ExpectedComp) => {
|
||||
const wrapper = createWrapper(selectedServer);
|
||||
const comp = wrapper.find(ExpectedComp);
|
||||
it('shows loading indicator while loading server', () => {
|
||||
setUp(null);
|
||||
|
||||
expect(comp).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns error if server is not reachable', () => {
|
||||
const wrapper = createWrapper(Mock.of<NonReachableServer>()).dive();
|
||||
const serverError = wrapper.find(ServerError);
|
||||
|
||||
expect(serverError).toHaveLength(1);
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
expect(screen.queryByText('ServerError')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['2.6.0' as SemVer, 10],
|
||||
['2.7.0' as SemVer, 10],
|
||||
['2.8.0' as SemVer, 11],
|
||||
['2.10.0' as SemVer, 11],
|
||||
['3.0.0' as SemVer, 12],
|
||||
['3.1.0' as SemVer, 13],
|
||||
])('has expected amount of routes based on selected server\'s version', (version, expectedAmountOfRoutes) => {
|
||||
const selectedServer = Mock.of<ReachableServer>({ version });
|
||||
const wrapper = createWrapper(selectedServer).dive();
|
||||
const routes = wrapper.find(Route);
|
||||
[Mock.of<NotFoundServer>({ serverNotFound: true })],
|
||||
[Mock.of<NonReachableServer>({ serverNotReachable: true })],
|
||||
])('shows error for non reachable servers', (selectedServer) => {
|
||||
setUp(selectedServer);
|
||||
|
||||
expect(routes).toHaveLength(expectedAmountOfRoutes);
|
||||
expect(routes.findWhere((element) => element.prop('index'))).toHaveLength(1);
|
||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('ServerError')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['3.0.0' as SemVer, '/overview', 'Overview'],
|
||||
['3.0.0' as SemVer, '/list-short-urls/1', 'ShortUrlsList'],
|
||||
['3.0.0' as SemVer, '/create-short-url', 'CreateShortUrl'],
|
||||
['3.0.0' as SemVer, '/short-code/abc123/visits/foo', 'ShortUrlVisits'],
|
||||
['3.0.0' as SemVer, '/short-code/abc123/edit', 'EditShortUrl'],
|
||||
['3.0.0' as SemVer, '/tag/foo/visits/foo', 'TagVisits'],
|
||||
['3.0.0' as SemVer, '/orphan-visits/foo', 'OrphanVisits'],
|
||||
['3.0.0' as SemVer, '/manage-tags', 'TagsList'],
|
||||
['3.0.0' as SemVer, '/not-found', 'Oops! We could not find requested route.'],
|
||||
['3.0.0' as SemVer, '/domain/domain.com/visits/foo', 'Oops! We could not find requested route.'],
|
||||
['3.1.0' as SemVer, '/domain/domain.com/visits/foo', 'DomainVisits'],
|
||||
['2.10.0' as SemVer, '/non-orphan-visits/foo', 'Oops! We could not find requested route.'],
|
||||
['3.0.0' as SemVer, '/non-orphan-visits/foo', 'NonOrphanVisits'],
|
||||
['2.7.0' as SemVer, '/manage-domains', 'Oops! We could not find requested route.'],
|
||||
['2.8.0' as SemVer, '/manage-domains', 'ManageDomains'],
|
||||
])(
|
||||
'renders expected component based on location and server version',
|
||||
(version, currentPath, expectedContent) => {
|
||||
setUp(Mock.of<ReachableServer>({ version }), currentPath);
|
||||
expect(screen.getByText(expectedContent)).toBeInTheDocument();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Link } from 'react-router-dom';
|
||||
import NotFound from '../../src/common/NotFound';
|
||||
import { NotFound } from '../../src/common/NotFound';
|
||||
import { SimpleCard } from '../../src/utils/SimpleCard';
|
||||
|
||||
describe('<NotFound />', () => {
|
||||
|
|
|
@ -9,12 +9,6 @@ import { Settings } from '../../src/settings/reducers/settings';
|
|||
import { ReportExporter } from '../../src/common/services/ReportExporter';
|
||||
import { SelectedServer } from '../../src/servers/data';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: jest.fn().mockReturnValue(jest.fn()),
|
||||
useParams: jest.fn().mockReturnValue({}),
|
||||
}));
|
||||
|
||||
describe('<NonOrphanVisits />', () => {
|
||||
const exportVisits = jest.fn();
|
||||
const getNonOrphanVisits = jest.fn();
|
||||
|
|
Loading…
Reference in a new issue