Finished migrating servers module to TS

This commit is contained in:
Alejandro Celaya 2020-08-29 20:20:45 +02:00
parent ef630af154
commit c0f5d9c12c
8 changed files with 130 additions and 142 deletions

View file

@ -1,5 +1,5 @@
import React, { FC, useEffect } from 'react'; import React, { FC, useEffect } from 'react';
import { Route, RouteChildrenProps, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import { EventData, Swipeable } from 'react-swipeable'; import { EventData, Swipeable } from 'react-swipeable';
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons'; import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@ -7,15 +7,11 @@ import classNames from 'classnames';
import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { withSelectedServer } from '../servers/helpers/withSelectedServer';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { versionMatch } from '../utils/helpers/version'; import { versionMatch } from '../utils/helpers/version';
import { isReachableServer, SelectedServer } from '../servers/data'; import { isReachableServer } from '../servers/data';
import NotFound from './NotFound'; import NotFound from './NotFound';
import { AsideMenuProps } from './AsideMenu'; import { AsideMenuProps } from './AsideMenu';
import './MenuLayout.scss'; import './MenuLayout.scss';
interface MenuLayoutProps extends RouteChildrenProps {
selectedServer: SelectedServer;
}
const MenuLayout = ( const MenuLayout = (
TagsList: FC, TagsList: FC,
ShortUrls: FC, ShortUrls: FC,
@ -25,7 +21,7 @@ const MenuLayout = (
TagVisits: FC, TagVisits: FC,
ShlinkVersions: FC, ShlinkVersions: FC,
ServerError: FC, ServerError: FC,
) => withSelectedServer(({ location, selectedServer }: MenuLayoutProps) => { ) => withSelectedServer(({ location, selectedServer }) => {
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle(); const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
useEffect(() => hideSidebar(), [ location ]); useEffect(() => hideSidebar(), [ location ]);

View file

@ -1,38 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'reactstrap';
import NoMenuLayout from '../common/NoMenuLayout';
import { ServerForm } from './helpers/ServerForm';
import { withSelectedServer } from './helpers/withSelectedServer';
import { serverType } from './prop-types';
const propTypes = {
editServer: PropTypes.func,
selectedServer: serverType,
history: PropTypes.shape({
push: PropTypes.func,
goBack: PropTypes.func,
}),
};
export const EditServer = (ServerError) => {
const EditServerComp = ({ editServer, selectedServer, history: { push, goBack } }) => {
const handleSubmit = (serverData) => {
editServer(selectedServer.id, serverData);
push(`/server/${selectedServer.id}/list-short-urls/1`);
};
return (
<NoMenuLayout>
<ServerForm initialValues={selectedServer} onSubmit={handleSubmit}>
<Button outline className="mr-2" onClick={goBack}>Cancel</Button>
<Button outline color="primary">Save</Button>
</ServerForm>
</NoMenuLayout>
);
};
EditServerComp.propTypes = propTypes;
return withSelectedServer(EditServerComp, ServerError);
};

View file

@ -0,0 +1,32 @@
import React, { FC } from 'react';
import { Button } from 'reactstrap';
import NoMenuLayout from '../common/NoMenuLayout';
import { ServerForm } from './helpers/ServerForm';
import { withSelectedServer } from './helpers/withSelectedServer';
import { isServerWithId, ServerData } from './data';
interface EditServerProps {
editServer: (serverId: string, serverData: ServerData) => void;
}
export const EditServer = (ServerError: FC) => withSelectedServer<EditServerProps>((
{ editServer, selectedServer, history: { push, goBack } },
) => {
if (!isServerWithId(selectedServer)) {
return null;
}
const handleSubmit = (serverData: ServerData) => {
editServer(selectedServer.id, serverData);
push(`/server/${selectedServer.id}/list-short-urls/1`);
};
return (
<NoMenuLayout>
<ServerForm initialValues={selectedServer} onSubmit={handleSubmit}>
<Button outline className="mr-2" onClick={goBack}>Cancel</Button>
<Button outline color="primary">Save</Button>
</ServerForm>
</NoMenuLayout>
);
}, ServerError);

View file

@ -1,55 +0,0 @@
import { isEmpty, values } from 'ramda';
import React from 'react';
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { serverType } from './prop-types';
const propTypes = {
servers: PropTypes.object,
selectedServer: serverType,
};
const ServersDropdown = (serversExporter) => {
const ServersDropdownComp = ({ servers, selectedServer }) => {
const serversList = values(servers);
const renderServers = () => {
if (isEmpty(serversList)) {
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>;
}
return (
<React.Fragment>
{serversList.map(({ name, id }) => (
<DropdownItem
key={id}
tag={Link}
to={`/server/${id}/list-short-urls/1`}
active={selectedServer && selectedServer.id === id}
>
{name}
</DropdownItem>
))}
<DropdownItem divider />
<DropdownItem className="servers-dropdown__export-item" onClick={() => serversExporter.exportServers()}>
Export servers
</DropdownItem>
</React.Fragment>
);
};
return (
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>Servers</DropdownToggle>
<DropdownMenu right>{renderServers()}</DropdownMenu>
</UncontrolledDropdown>
);
};
ServersDropdownComp.propTypes = propTypes;
return ServersDropdownComp;
};
export default ServersDropdown;

View file

@ -0,0 +1,49 @@
import { isEmpty, values } from 'ramda';
import React from 'react';
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
import { Link } from 'react-router-dom';
import ServersExporter from './services/ServersExporter';
import { isServerWithId, SelectedServer, ServersMap } from './data';
export interface ServersDropdownProps {
servers: ServersMap;
selectedServer: SelectedServer;
}
const ServersDropdown = (serversExporter: ServersExporter) => ({ servers, selectedServer }: ServersDropdownProps) => {
const serversList = values(servers);
const renderServers = () => {
if (isEmpty(serversList)) {
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>;
}
return (
<React.Fragment>
{serversList.map(({ name, id }) => (
<DropdownItem
key={id}
tag={Link}
to={`/server/${id}/list-short-urls/1`}
active={isServerWithId(selectedServer) && selectedServer.id === id}
>
{name}
</DropdownItem>
))}
<DropdownItem divider />
<DropdownItem className="servers-dropdown__export-item" onClick={async () => serversExporter.exportServers()}>
Export servers
</DropdownItem>
</React.Fragment>
);
};
return (
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>Servers</DropdownToggle>
<DropdownMenu right>{renderServers()}</DropdownMenu>
</UncontrolledDropdown>
);
};
export default ServersDropdown;

View file

@ -8,9 +8,8 @@ interface WithSelectedServerProps extends RouteChildrenProps<{ serverId: string
selectedServer: SelectedServer; selectedServer: SelectedServer;
} }
export const withSelectedServer = (WrappedComponent: FC<WithSelectedServerProps>, ServerError: FC) => ( export function withSelectedServer<T = {}>(WrappedComponent: FC<WithSelectedServerProps & T>, ServerError: FC) {
props: WithSelectedServerProps, return (props: WithSelectedServerProps & T) => {
) => {
const { selectServer, selectedServer, match } = props; const { selectServer, selectedServer, match } = props;
useEffect(() => { useEffect(() => {
@ -26,4 +25,5 @@ export const withSelectedServer = (WrappedComponent: FC<WithSelectedServerProps>
} }
return <WrappedComponent {...props} />; return <WrappedComponent {...props} />;
}; };
}

View file

@ -1,22 +1,27 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount, ReactWrapper } from 'enzyme';
import { Mock } from 'ts-mockery';
import { History, Location } from 'history';
import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars
import { EditServer as editServerConstruct } from '../../src/servers/EditServer'; import { EditServer as editServerConstruct } from '../../src/servers/EditServer';
import { ServerForm } from '../../src/servers/helpers/ServerForm'; import { ServerForm } from '../../src/servers/helpers/ServerForm';
import { ReachableServer } from '../../src/servers/data';
describe('<EditServer />', () => { describe('<EditServer />', () => {
let wrapper; let wrapper: ReactWrapper;
const ServerError = jest.fn(); const ServerError = jest.fn();
const editServerMock = jest.fn(); const editServerMock = jest.fn();
const historyMock = { push: jest.fn() }; const push = jest.fn();
const match = { const historyMock = Mock.of<History>({ push });
const match = Mock.of<match<{ serverId: string }>>({
params: { serverId: 'abc123' }, params: { serverId: 'abc123' },
}; });
const selectedServer = { const selectedServer = Mock.of<ReachableServer>({
id: 'abc123', id: 'abc123',
name: 'name', name: 'name',
url: 'url', url: 'url',
apiKey: 'apiKey', apiKey: 'apiKey',
}; });
beforeEach(() => { beforeEach(() => {
const EditServer = editServerConstruct(ServerError); const EditServer = editServerConstruct(ServerError);
@ -26,16 +31,15 @@ describe('<EditServer />', () => {
editServer={editServerMock} editServer={editServerMock}
history={historyMock} history={historyMock}
match={match} match={match}
location={Mock.all<Location>()}
selectedServer={selectedServer} selectedServer={selectedServer}
selectServer={jest.fn()} selectServer={jest.fn()}
/>, />,
); );
}); });
afterEach(() => { afterEach(jest.resetAllMocks);
jest.resetAllMocks(); afterEach(() => wrapper?.unmount());
wrapper && wrapper.unmount();
});
it('renders components', () => { it('renders components', () => {
expect(wrapper.find(ServerForm)).toHaveLength(1); expect(wrapper.find(ServerForm)).toHaveLength(1);
@ -47,6 +51,6 @@ describe('<EditServer />', () => {
form.simulate('submit', {}); form.simulate('submit', {});
expect(editServerMock).toHaveBeenCalledTimes(1); expect(editServerMock).toHaveBeenCalledTimes(1);
expect(historyMock.push).toHaveBeenCalledTimes(1); expect(push).toHaveBeenCalledTimes(1);
}); });
}); });

View file

@ -1,24 +1,24 @@
import { identity, values } from 'ramda'; import { values } from 'ramda';
import React from 'react'; import { Mock } from 'ts-mockery';
import { shallow } from 'enzyme'; import React, { FC } from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { DropdownItem, DropdownToggle } from 'reactstrap'; import { DropdownItem, DropdownToggle } from 'reactstrap';
import serversDropdownCreator from '../../src/servers/ServersDropdown'; import serversDropdownCreator, { ServersDropdownProps } from '../../src/servers/ServersDropdown';
import { ServerWithId } from '../../src/servers/data';
import ServersExporter from '../../src/servers/services/ServersExporter';
describe('<ServersDropdown />', () => { describe('<ServersDropdown />', () => {
let wrapped; let wrapped: ShallowWrapper;
let ServersDropdown; let ServersDropdown: FC<ServersDropdownProps>;
const servers = { const servers = {
'1a': { name: 'foo', id: 1 }, '1a': Mock.of<ServerWithId>({ name: 'foo', id: '1a' }),
'2b': { name: 'bar', id: 2 }, '2b': Mock.of<ServerWithId>({ name: 'bar', id: '2b' }),
'3c': { name: 'baz', id: 3 }, '3c': Mock.of<ServerWithId>({ name: 'baz', id: '3c' }),
};
const history = {
push: jest.fn(),
}; };
beforeEach(() => { beforeEach(() => {
ServersDropdown = serversDropdownCreator({}); ServersDropdown = serversDropdownCreator(Mock.of<ServersExporter>());
wrapped = shallow(<ServersDropdown servers={servers} listServers={identity} history={history} />); wrapped = shallow(<ServersDropdown servers={servers} selectedServer={null} />);
}); });
afterEach(() => wrapped.unmount()); afterEach(() => wrapped.unmount());
@ -37,7 +37,7 @@ describe('<ServersDropdown />', () => {
it('shows a message when no servers exist yet', () => { it('shows a message when no servers exist yet', () => {
wrapped = shallow( wrapped = shallow(
<ServersDropdown servers={{}} listServers={identity} history={history} />, <ServersDropdown servers={{}} selectedServer={null} />,
); );
const item = wrapped.find(DropdownItem); const item = wrapped.find(DropdownItem);