mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-03 14:57:22 +03:00
Migrated some common components and their dependencies to TS
This commit is contained in:
parent
a96539129d
commit
f40ad91ea9
25 changed files with 274 additions and 322 deletions
2
shlink-web-client.d.ts
vendored
2
shlink-web-client.d.ts
vendored
|
@ -1,4 +1,6 @@
|
|||
export declare global {
|
||||
declare module '*.png'
|
||||
|
||||
interface Window {
|
||||
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: Function;
|
||||
}
|
||||
|
|
|
@ -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
77
src/common/AsideMenu.tsx
Normal 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;
|
|
@ -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;
|
|
@ -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} /> Settings
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink tag={Link} to={createServerPath} active={pathname === createServerPath}>
|
||||
<FontAwesomeIcon icon={plusIcon} /> Add server
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<ServersDropdown />
|
||||
</Nav>
|
||||
</Collapse>
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
MainHeaderComp.propTypes = propTypes;
|
||||
|
||||
return MainHeaderComp;
|
||||
};
|
||||
|
||||
export default MainHeader;
|
51
src/common/MainHeader.tsx
Normal file
51
src/common/MainHeader.tsx
Normal 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} /> Settings
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink tag={Link} to={createServerPath} active={pathname === createServerPath}>
|
||||
<FontAwesomeIcon icon={plusIcon} /> Add server
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<ServersDropdown />
|
||||
</Nav>
|
||||
</Collapse>
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainHeader;
|
|
@ -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;
|
6
src/common/NoMenuLayout.tsx
Normal file
6
src/common/NoMenuLayout.tsx
Normal 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;
|
|
@ -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;
|
|
@ -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;
|
12
src/common/ScrollToTop.tsx
Normal file
12
src/common/ScrollToTop.tsx
Normal 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;
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
31
src/servers/DeleteServerButton.tsx
Normal file
31
src/servers/DeleteServerButton.tsx
Normal 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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
@ -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');
|
|
@ -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();
|
|
@ -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'));
|
||||
});
|
19
test/common/ScrollToTop.test.tsx
Normal file
19
test/common/ScrollToTop.test.tsx
Normal 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'));
|
||||
});
|
|
@ -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());
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
|
Loading…
Reference in a new issue