Migrated some common components and their dependencies to TS

This commit is contained in:
Alejandro Celaya 2020-08-29 09:19:15 +02:00
parent a96539129d
commit f40ad91ea9
25 changed files with 274 additions and 322 deletions

View file

@ -1,4 +1,6 @@
export declare global {
declare module '*.png'
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: Function;
}

View file

@ -1,81 +0,0 @@
import {
faList as listIcon,
faLink as createIcon,
faTags as tagsIcon,
faPen as editIcon,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React from 'react';
import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { serverType } from '../servers/prop-types';
import './AsideMenu.scss';
const AsideMenuItem = ({ children, to, className, ...rest }) => (
<NavLink
className={classNames('aside-menu__item', className)}
activeClassName="aside-menu__item--selected"
to={to}
{...rest}
>
{children}
</NavLink>
);
AsideMenuItem.propTypes = {
children: PropTypes.node.isRequired,
to: PropTypes.string.isRequired,
className: PropTypes.string,
};
const propTypes = {
selectedServer: serverType,
className: PropTypes.string,
showOnMobile: PropTypes.bool,
};
const AsideMenu = (DeleteServerButton) => {
const AsideMenu = ({ selectedServer, className, showOnMobile }) => {
const serverId = selectedServer ? selectedServer.id : '';
const asideClass = classNames('aside-menu', className, {
'aside-menu--hidden': !showOnMobile,
});
const shortUrlsIsActive = (match, location) => location.pathname.match('/list-short-urls');
const buildPath = (suffix) => `/server/${serverId}${suffix}`;
return (
<aside className={asideClass}>
<nav className="nav flex-column aside-menu__nav">
<AsideMenuItem to={buildPath('/list-short-urls/1')} isActive={shortUrlsIsActive}>
<FontAwesomeIcon icon={listIcon} />
<span className="aside-menu__item-text">List short URLs</span>
</AsideMenuItem>
<AsideMenuItem to={buildPath('/create-short-url')}>
<FontAwesomeIcon icon={createIcon} flip="horizontal" />
<span className="aside-menu__item-text">Create short URL</span>
</AsideMenuItem>
<AsideMenuItem to={buildPath('/manage-tags')}>
<FontAwesomeIcon icon={tagsIcon} />
<span className="aside-menu__item-text">Manage tags</span>
</AsideMenuItem>
<AsideMenuItem to={buildPath('/edit')} className="aside-menu__item--push">
<FontAwesomeIcon icon={editIcon} />
<span className="aside-menu__item-text">Edit this server</span>
</AsideMenuItem>
<DeleteServerButton
className="aside-menu__item aside-menu__item--danger"
textClassName="aside-menu__item-text"
server={selectedServer}
/>
</nav>
</aside>
);
};
AsideMenu.propTypes = propTypes;
return AsideMenu;
};
export default AsideMenu;

77
src/common/AsideMenu.tsx Normal file
View file

@ -0,0 +1,77 @@
import {
faList as listIcon,
faLink as createIcon,
faTags as tagsIcon,
faPen as editIcon,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { FC } from 'react';
import { NavLink, NavLinkProps } from 'react-router-dom';
import classNames from 'classnames';
import { Location } from 'history';
import { DeleteServerButtonProps } from '../servers/DeleteServerButton';
import { ServerWithId } from '../servers/data';
import './AsideMenu.scss';
interface AsideMenuProps {
selectedServer: ServerWithId;
className?: string;
showOnMobile?: boolean;
}
interface AsideMenuItemItemProps extends NavLinkProps {
to: string;
className?: string;
}
const AsideMenuItem: FC<AsideMenuItemItemProps> = ({ children, to, className, ...rest }) => (
<NavLink
className={classNames('aside-menu__item', className)}
activeClassName="aside-menu__item--selected"
to={to}
{...rest}
>
{children}
</NavLink>
);
const AsideMenu = (DeleteServerButton: FC<DeleteServerButtonProps>) => (
{ selectedServer, className, showOnMobile = false }: AsideMenuProps,
) => {
const serverId = selectedServer ? selectedServer.id : '';
const asideClass = classNames('aside-menu', className, {
'aside-menu--hidden': !showOnMobile,
});
const shortUrlsIsActive = (_: null, location: Location) => location.pathname.match('/list-short-urls') !== null;
const buildPath = (suffix: string) => `/server/${serverId}${suffix}`;
return (
<aside className={asideClass}>
<nav className="nav flex-column aside-menu__nav">
<AsideMenuItem to={buildPath('/list-short-urls/1')} isActive={shortUrlsIsActive}>
<FontAwesomeIcon icon={listIcon} />
<span className="aside-menu__item-text">List short URLs</span>
</AsideMenuItem>
<AsideMenuItem to={buildPath('/create-short-url')}>
<FontAwesomeIcon icon={createIcon} flip="horizontal" />
<span className="aside-menu__item-text">Create short URL</span>
</AsideMenuItem>
<AsideMenuItem to={buildPath('/manage-tags')}>
<FontAwesomeIcon icon={tagsIcon} />
<span className="aside-menu__item-text">Manage tags</span>
</AsideMenuItem>
<AsideMenuItem to={buildPath('/edit')} className="aside-menu__item--push">
<FontAwesomeIcon icon={editIcon} />
<span className="aside-menu__item-text">Edit this server</span>
</AsideMenuItem>
<DeleteServerButton
className="aside-menu__item aside-menu__item--danger"
textClassName="aside-menu__item-text"
server={selectedServer}
/>
</nav>
</aside>
);
};
export default AsideMenu;

View file

@ -1,16 +1,16 @@
import React, { useEffect } from 'react';
import { isEmpty, values } from 'ramda';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import './Home.scss';
import ServersListGroup from '../servers/ServersListGroup';
import { ServersMap } from '../servers/reducers/servers';
import './Home.scss';
const propTypes = {
resetSelectedServer: PropTypes.func,
servers: PropTypes.object,
};
export interface HomeProps {
resetSelectedServer: Function;
servers: ServersMap;
}
const Home = ({ resetSelectedServer, servers }) => {
const Home = ({ resetSelectedServer, servers }: HomeProps) => {
const serversList = values(servers);
const hasServers = !isEmpty(serversList);
@ -29,6 +29,4 @@ const Home = ({ resetSelectedServer, servers }) => {
);
};
Home.propTypes = propTypes;
export default Home;

View file

@ -1,61 +0,0 @@
import { faPlus as plusIcon, faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { useToggle } from '../utils/helpers/hooks';
import shlinkLogo from './shlink-logo-white.png';
import './MainHeader.scss';
const propTypes = {
location: PropTypes.object,
};
const MainHeader = (ServersDropdown) => {
const MainHeaderComp = ({ location }) => {
const [ isOpen, toggleOpen, , close ] = useToggle();
const { pathname } = location;
useEffect(close, [ location ]);
const createServerPath = '/server/create';
const settingsPath = '/settings';
const toggleClass = classNames('main-header__toggle-icon', { 'main-header__toggle-icon--opened': isOpen });
return (
<Navbar color="primary" dark fixed="top" className="main-header" expand="md">
<NavbarBrand tag={Link} to="/">
<img src={shlinkLogo} alt="Shlink" className="main-header__brand-logo" /> Shlink
</NavbarBrand>
<NavbarToggler onClick={toggleOpen}>
<FontAwesomeIcon icon={arrowIcon} className={toggleClass} />
</NavbarToggler>
<Collapse navbar isOpen={isOpen}>
<Nav navbar className="ml-auto">
<NavItem>
<NavLink tag={Link} to={settingsPath} active={pathname === settingsPath}>
<FontAwesomeIcon icon={cogsIcon} />&nbsp; Settings
</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} to={createServerPath} active={pathname === createServerPath}>
<FontAwesomeIcon icon={plusIcon} />&nbsp; Add server
</NavLink>
</NavItem>
<ServersDropdown />
</Nav>
</Collapse>
</Navbar>
);
};
MainHeaderComp.propTypes = propTypes;
return MainHeaderComp;
};
export default MainHeader;

51
src/common/MainHeader.tsx Normal file
View file

@ -0,0 +1,51 @@
import { faPlus as plusIcon, faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { FC, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import classNames from 'classnames';
import { RouteChildrenProps } from 'react-router';
import { useToggle } from '../utils/helpers/hooks';
import shlinkLogo from './shlink-logo-white.png';
import './MainHeader.scss';
const MainHeader = (ServersDropdown: FC) => ({ location }: RouteChildrenProps) => {
const [ isOpen, toggleOpen, , close ] = useToggle();
const { pathname } = location;
useEffect(close, [ location ]);
const createServerPath = '/server/create';
const settingsPath = '/settings';
const toggleClass = classNames('main-header__toggle-icon', { 'main-header__toggle-icon--opened': isOpen });
return (
<Navbar color="primary" dark fixed="top" className="main-header" expand="md">
<NavbarBrand tag={Link} to="/">
<img src={shlinkLogo} alt="Shlink" className="main-header__brand-logo" /> Shlink
</NavbarBrand>
<NavbarToggler onClick={toggleOpen}>
<FontAwesomeIcon icon={arrowIcon} className={toggleClass} />
</NavbarToggler>
<Collapse navbar isOpen={isOpen}>
<Nav navbar className="ml-auto">
<NavItem>
<NavLink tag={Link} to={settingsPath} active={pathname === settingsPath}>
<FontAwesomeIcon icon={cogsIcon} />&nbsp; Settings
</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} to={createServerPath} active={pathname === createServerPath}>
<FontAwesomeIcon icon={plusIcon} />&nbsp; Add server
</NavLink>
</NavItem>
<ServersDropdown />
</Nav>
</Collapse>
</Navbar>
);
};
export default MainHeader;

View file

@ -1,13 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import './NoMenuLayout.scss';
const propTypes = {
children: PropTypes.node,
};
const NoMenuLayout = ({ children }) => <div className="no-menu-wrapper">{children}</div>;
NoMenuLayout.propTypes = propTypes;
export default NoMenuLayout;

View file

@ -0,0 +1,6 @@
import React, { FC } from 'react';
import './NoMenuLayout.scss';
const NoMenuLayout: FC = ({ children }) => <div className="no-menu-wrapper">{children}</div>;
export default NoMenuLayout;

View file

@ -1,13 +1,11 @@
import React from 'react';
import React, { FC } from 'react';
import { Link } from 'react-router-dom';
import * as PropTypes from 'prop-types';
const propTypes = {
to: PropTypes.string,
children: PropTypes.node,
};
interface NotFoundProps {
to?: string;
}
const NotFound = ({ to = '/', children = 'Home' }) => (
const NotFound: FC<NotFoundProps> = ({ to = '/', children = 'Home' }) => (
<div className="home">
<h2>Oops! We could not find requested route.</h2>
<p>
@ -19,6 +17,4 @@ const NotFound = ({ to = '/', children = 'Home' }) => (
</div>
);
NotFound.propTypes = propTypes;
export default NotFound;

View file

@ -1,23 +0,0 @@
import { useEffect } from 'react';
import PropTypes from 'prop-types';
const propTypes = {
location: PropTypes.object,
children: PropTypes.node,
};
const ScrollToTop = () => {
const ScrollToTopComp = ({ location, children }) => {
useEffect(() => {
scrollTo(0, 0);
}, [ location ]);
return children;
};
ScrollToTopComp.propTypes = propTypes;
return ScrollToTopComp;
};
export default ScrollToTop;

View file

@ -0,0 +1,12 @@
import React, { PropsWithChildren, useEffect } from 'react';
import { RouteChildrenProps } from 'react-router';
const ScrollToTop = () => ({ location, children }: PropsWithChildren<RouteChildrenProps>) => {
useEffect(() => {
scrollTo(0, 0);
}, [ location ]);
return <React.Fragment>{children}</React.Fragment>;
};
export default ScrollToTop;

View file

@ -12,7 +12,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
bottle.constant('window', (global as any).window);
bottle.constant('console', global.console);
bottle.serviceFactory('ScrollToTop', ScrollToTop, 'window');
bottle.serviceFactory('ScrollToTop', ScrollToTop);
bottle.decorator('ScrollToTop', withRouter);
bottle.serviceFactory('MainHeader', MainHeader, 'ServersDropdown');

View file

@ -1,36 +0,0 @@
import React from 'react';
import { faMinusCircle as deleteIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
import { useToggle } from '../utils/helpers/hooks';
import { serverType } from './prop-types';
const propTypes = {
server: serverType,
className: PropTypes.string,
textClassName: PropTypes.string,
children: PropTypes.node,
};
const DeleteServerButton = (DeleteServerModal) => {
const DeleteServerButtonComp = ({ server, className, children, textClassName }) => {
const [ isModalOpen, , showModal, hideModal ] = useToggle();
return (
<React.Fragment>
<span className={className} onClick={showModal}>
{!children && <FontAwesomeIcon icon={deleteIcon} />}
<span className={textClassName}>{children || 'Remove this server'}</span>
</span>
<DeleteServerModal server={server} isOpen={isModalOpen} toggle={hideModal} />
</React.Fragment>
);
};
DeleteServerButtonComp.propTypes = propTypes;
return DeleteServerButtonComp;
};
export default DeleteServerButton;

View file

@ -0,0 +1,31 @@
import React, { FC } from 'react';
import { faMinusCircle as deleteIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useToggle } from '../utils/helpers/hooks';
import { DeleteServerModalProps } from './DeleteServerModal';
import { ServerWithId } from './data';
export interface DeleteServerButtonProps {
server: ServerWithId;
className?: string;
textClassName?: string;
}
const DeleteServerButton = (DeleteServerModal: FC<DeleteServerModalProps>): FC<DeleteServerButtonProps> => (
{ server, className, children, textClassName },
) => {
const [ isModalOpen, , showModal, hideModal ] = useToggle();
return (
<React.Fragment>
<span className={className} onClick={showModal}>
{!children && <FontAwesomeIcon icon={deleteIcon} />}
<span className={textClassName}>{children ?? 'Remove this server'}</span>
</span>
<DeleteServerModal server={server} isOpen={isModalOpen} toggle={hideModal} />
</React.Fragment>
);
};
export default DeleteServerButton;

View file

@ -1,19 +1,19 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { serverType } from './prop-types';
import { RouterProps } from 'react-router';
import { ServerWithId } from './data';
const propTypes = {
toggle: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
server: serverType,
deleteServer: PropTypes.func,
history: PropTypes.shape({
push: PropTypes.func,
}),
};
export interface DeleteServerModalProps {
server: ServerWithId;
toggle: () => void;
isOpen: boolean;
}
const DeleteServerModal = ({ server, toggle, isOpen, deleteServer, history }) => {
interface DeleteServerModalConnectProps extends DeleteServerModalProps, RouterProps {
deleteServer: (server: ServerWithId) => void;
}
const DeleteServerModal = ({ server, toggle, isOpen, deleteServer, history }: DeleteServerModalConnectProps) => {
const closeModal = () => {
deleteServer(server);
toggle();
@ -40,6 +40,4 @@ const DeleteServerModal = ({ server, toggle, isOpen, deleteServer, history }) =>
);
};
DeleteServerModal.propTypes = propTypes;
export default DeleteServerModal;

View file

@ -1,30 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import React, { FC } from 'react';
import { ListGroup, ListGroupItem } from 'reactstrap';
import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons';
import { serverType } from './prop-types';
import './ServersListGroup.scss';
import { ServerWithId } from './data';
const propTypes = {
servers: PropTypes.arrayOf(serverType).isRequired,
children: PropTypes.node.isRequired,
};
interface ServersListGroup {
servers: ServerWithId[];
}
const ServerListItem = ({ id, name }) => (
const ServerListItem = ({ id, name }: { id: string; name: string }) => (
<ListGroupItem tag={Link} to={`/server/${id}/list-short-urls/1`} className="servers-list__server-item">
{name}
<FontAwesomeIcon icon={chevronIcon} className="servers-list__server-item-icon" />
</ListGroupItem>
);
ServerListItem.propTypes = {
id: PropTypes.string,
name: PropTypes.string,
};
const ServersListGroup = ({ servers, children }) => (
const ServersListGroup: FC<ServersListGroup> = ({ servers, children }) => (
<React.Fragment>
<div className="container">
<h5>{children}</h5>
@ -37,6 +30,4 @@ const ServersListGroup = ({ servers, children }) => (
</React.Fragment>
);
ServersListGroup.propTypes = propTypes;
export default ServersListGroup;

View file

@ -23,7 +23,7 @@ export const useStateFlagTimeout = (
return [ flag, callback ];
};
type ToggleResult = [ boolean, (flag: boolean) => void, () => void, () => void ];
type ToggleResult = [ boolean, () => void, () => void, () => void ];
export const useToggle = (initialValue = false): ToggleResult => {
const [ flag, setFlag ] = useState<boolean>(initialValue);

View file

@ -1,15 +1,17 @@
import { shallow } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';
import React from 'react';
import { Mock } from 'ts-mockery';
import asideMenuCreator from '../../src/common/AsideMenu';
import { ServerWithId } from '../../src/servers/data';
describe('<AsideMenu />', () => {
let wrapped;
const DeleteServerButton = () => '';
let wrapped: ShallowWrapper;
const DeleteServerButton = () => null;
beforeEach(() => {
const AsideMenu = asideMenuCreator(DeleteServerButton);
wrapped = shallow(<AsideMenu selectedServer={{ id: 'abc123' }} />);
wrapped = shallow(<AsideMenu selectedServer={Mock.of<ServerWithId>({ id: 'abc123' })} />);
});
afterEach(() => wrapped.unmount());

View file

@ -1,14 +1,16 @@
import { shallow } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';
import React from 'react';
import Home from '../../src/common/Home';
import { Mock } from 'ts-mockery';
import Home, { HomeProps } from '../../src/common/Home';
import { ServerWithId } from '../../src/servers/data';
describe('<Home />', () => {
let wrapped;
let wrapped: ShallowWrapper;
const defaultProps = {
resetSelectedServer: jest.fn(),
servers: {},
};
const createComponent = (props) => {
const createComponent = (props: Partial<HomeProps> = {}) => {
const actualProps = { ...defaultProps, ...props };
wrapped = shallow(<Home {...actualProps} />);
@ -16,7 +18,7 @@ describe('<Home />', () => {
return wrapped;
};
afterEach(() => wrapped && wrapped.unmount());
afterEach(() => wrapped?.unmount());
it('shows link to create server when no servers exist', () => {
const wrapped = createComponent();
@ -26,8 +28,8 @@ describe('<Home />', () => {
it('asks to select a server when servers exist', () => {
const servers = {
1: { name: 'foo', id: '1' },
2: { name: 'bar', id: '2' },
'1a': Mock.of<ServerWithId>({ name: 'foo', id: '1' }),
'2b': Mock.of<ServerWithId>({ name: 'bar', id: '2' }),
};
const wrapped = createComponent({ servers });
const span = wrapped.find('span');

View file

@ -1,10 +1,10 @@
import React from 'react';
import { shallow } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';
import { Link } from 'react-router-dom';
import NotFound from '../../src/common/NotFound';
describe('<NotFound />', () => {
let wrapper;
let wrapper: ShallowWrapper;
const createWrapper = (props = {}) => {
wrapper = shallow(<NotFound {...props} />);
const content = wrapper.text();
@ -12,7 +12,7 @@ describe('<NotFound />', () => {
return { wrapper, content };
};
afterEach(() => wrapper && wrapper.unmount());
afterEach(() => wrapper?.unmount());
it('shows expected error title', () => {
const { content } = createWrapper();

View file

@ -1,23 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import createScrollToTop from '../../src/common/ScrollToTop';
describe('<ScrollToTop />', () => {
let wrapper;
const window = {
scrollTo: jest.fn(),
};
beforeEach(() => {
const ScrollToTop = createScrollToTop(window);
wrapper = shallow(<ScrollToTop locaction={{ href: 'foo' }}>Foobar</ScrollToTop>);
});
afterEach(() => {
wrapper.unmount();
window.scrollTo.mockReset();
});
it('just renders children', () => expect(wrapper.text()).toEqual('Foobar'));
});

View file

@ -0,0 +1,19 @@
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { Mock } from 'ts-mockery';
import { RouteChildrenProps } from 'react-router';
import createScrollToTop from '../../src/common/ScrollToTop';
describe('<ScrollToTop />', () => {
let wrapper: ShallowWrapper;
beforeEach(() => {
const ScrollToTop = createScrollToTop();
wrapper = shallow(<ScrollToTop {...Mock.all<RouteChildrenProps>()}>Foobar</ScrollToTop>);
});
afterEach(() => wrapper.unmount());
it('just renders children', () => expect(wrapper.text()).toEqual('Foobar'));
});

View file

@ -1,15 +1,17 @@
import React from 'react';
import { shallow } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';
import { Mock } from 'ts-mockery';
import deleteServerButtonConstruct from '../../src/servers/DeleteServerButton';
import DeleteServerModal from '../../src/servers/DeleteServerModal';
import { ServerWithId } from '../../src/servers/data';
describe('<DeleteServerButton />', () => {
let wrapper;
let wrapper: ShallowWrapper;
const DeleteServerModal = () => null;
beforeEach(() => {
const DeleteServerButton = deleteServerButtonConstruct(DeleteServerModal);
wrapper = shallow(<DeleteServerButton server={{}} className="button" />);
wrapper = shallow(<DeleteServerButton server={Mock.all<ServerWithId>()} className="button" />);
});
afterEach(() => wrapper.unmount());

View file

@ -1,31 +1,31 @@
import React from 'react';
import { shallow } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { History } from 'history';
import { Mock } from 'ts-mockery';
import DeleteServerModal from '../../src/servers/DeleteServerModal';
import { ServerWithId } from '../../src/servers/data';
describe('<DeleteServerModal />', () => {
let wrapper;
let wrapper: ShallowWrapper;
const deleteServerMock = jest.fn();
const historyMock = { push: jest.fn() };
const push = jest.fn();
const toggleMock = jest.fn();
const serverName = 'the_server_name';
beforeEach(() => {
deleteServerMock.mockReset();
toggleMock.mockReset();
historyMock.push.mockReset();
wrapper = shallow(
<DeleteServerModal
server={{ name: serverName }}
server={Mock.of<ServerWithId>({ name: serverName })}
toggle={toggleMock}
isOpen={true}
deleteServer={deleteServerMock}
history={historyMock}
history={Mock.of<History>({ push })}
/>,
);
});
afterEach(() => wrapper.unmount());
afterEach(jest.clearAllMocks);
it('renders a modal window', () => {
expect(wrapper.find(Modal)).toHaveLength(1);
@ -49,7 +49,7 @@ describe('<DeleteServerModal />', () => {
expect(toggleMock).toHaveBeenCalledTimes(1);
expect(deleteServerMock).not.toHaveBeenCalled();
expect(historyMock.push).not.toHaveBeenCalled();
expect(push).not.toHaveBeenCalled();
});
it('deletes server when clicking accept button', () => {
@ -59,6 +59,6 @@ describe('<DeleteServerModal />', () => {
expect(toggleMock).toHaveBeenCalledTimes(1);
expect(deleteServerMock).toHaveBeenCalledTimes(1);
expect(historyMock.push).toHaveBeenCalledTimes(1);
expect(push).toHaveBeenCalledTimes(1);
});
});

View file

@ -1,17 +1,19 @@
import { shallow } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';
import React from 'react';
import { ListGroup } from 'reactstrap';
import { Mock } from 'ts-mockery';
import ServersListGroup from '../../src/servers/ServersListGroup';
import { ServerWithId } from '../../src/servers/data';
describe('<ServersListGroup />', () => {
let wrapped;
const createComponent = (servers) => {
let wrapped: ShallowWrapper;
const createComponent = (servers: ServerWithId[]) => {
wrapped = shallow(<ServersListGroup servers={servers}>The list of servers</ServersListGroup>);
return wrapped;
};
afterEach(() => wrapped && wrapped.unmount());
afterEach(() => wrapped?.unmount());
it('Renders title', () => {
const wrapped = createComponent([]);
@ -23,8 +25,8 @@ describe('<ServersListGroup />', () => {
it('shows servers list', () => {
const servers = [
{ name: 'foo', id: '123' },
{ name: 'bar', id: '456' },
Mock.of<ServerWithId>({ name: 'foo', id: '123' }),
Mock.of<ServerWithId>({ name: 'bar', id: '456' }),
];
const wrapped = createComponent(servers);