mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-12 02:23:49 +00:00
Merge pull request #659 from acelaya-forks/feature/moar-rtl
Feature/moar rtl
This commit is contained in:
@@ -26,7 +26,7 @@ export const DeleteServerModal: FC<DeleteServerModalConnectProps> = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} toggle={toggle} centered>
|
<Modal isOpen={isOpen} toggle={toggle} centered>
|
||||||
<ModalHeader toggle={toggle}><span className="text-danger">Remove server</span></ModalHeader>
|
<ModalHeader toggle={toggle} className="text-danger">Remove server</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<p>Are you sure you want to remove <b>{server ? server.name : ''}</b>?</p>
|
<p>Are you sure you want to remove <b>{server ? server.name : ''}</b>?</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const ManageServersRowDropdown = (
|
|||||||
<DropdownItem tag={Link} to={`${serverUrl}/edit`}>
|
<DropdownItem tag={Link} to={`${serverUrl}/edit`}>
|
||||||
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit server
|
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit server
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem onClick={() => setAutoConnect(server, !server.autoConnect)}>
|
<DropdownItem onClick={() => setAutoConnect(server, !isAutoConnect)}>
|
||||||
<FontAwesomeIcon icon={autoConnectIcon} fixedWidth /> {isAutoConnect ? 'Do not a' : 'A'}uto-connect
|
<FontAwesomeIcon icon={autoConnectIcon} fixedWidth /> {isAutoConnect ? 'Do not a' : 'A'}uto-connect
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem divider />
|
<DropdownItem divider />
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { parseISO, format as formatDate, getUnixTime, formatDistance } from 'date-fns';
|
import { parseISO, format as formatDate, getUnixTime, formatDistance } from 'date-fns';
|
||||||
import { isDateObject } from './helpers/date';
|
import { isDateObject } from './helpers/date';
|
||||||
|
|
||||||
export interface DateProps {
|
export interface TimeProps {
|
||||||
date: Date | string;
|
date: Date | string;
|
||||||
format?: string;
|
format?: string;
|
||||||
relative?: boolean;
|
relative?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Time = ({ date, format = 'yyyy-MM-dd HH:mm', relative = false }: DateProps) => {
|
export const Time = ({ date, format = 'yyyy-MM-dd HH:mm', relative = false }: TimeProps) => {
|
||||||
const dateObject = isDateObject(date) ? date : parseISO(date);
|
const dateObject = isDateObject(date) ? date : parseISO(date);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { DeleteServerModal } from '../../src/servers/DeleteServerModal';
|
import { DeleteServerModal } from '../../src/servers/DeleteServerModal';
|
||||||
@@ -8,56 +8,63 @@ import { ServerWithId } from '../../src/servers/data';
|
|||||||
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() }));
|
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() }));
|
||||||
|
|
||||||
describe('<DeleteServerModal />', () => {
|
describe('<DeleteServerModal />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
|
||||||
const deleteServerMock = jest.fn();
|
const deleteServerMock = jest.fn();
|
||||||
const navigate = jest.fn();
|
const navigate = jest.fn();
|
||||||
const toggleMock = jest.fn();
|
const toggleMock = jest.fn();
|
||||||
const serverName = 'the_server_name';
|
const serverName = 'the_server_name';
|
||||||
|
const setUp = () => {
|
||||||
beforeEach(() => {
|
|
||||||
(useNavigate as any).mockReturnValue(navigate);
|
(useNavigate as any).mockReturnValue(navigate);
|
||||||
|
|
||||||
wrapper = shallow(
|
return {
|
||||||
<DeleteServerModal
|
user: userEvent.setup(),
|
||||||
server={Mock.of<ServerWithId>({ name: serverName })}
|
...render(
|
||||||
toggle={toggleMock}
|
<DeleteServerModal
|
||||||
isOpen
|
server={Mock.of<ServerWithId>({ name: serverName })}
|
||||||
deleteServer={deleteServerMock}
|
toggle={toggleMock}
|
||||||
/>,
|
isOpen
|
||||||
);
|
deleteServer={deleteServerMock}
|
||||||
});
|
/>,
|
||||||
afterEach(() => wrapper.unmount());
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
afterEach(jest.clearAllMocks);
|
||||||
|
|
||||||
it('renders a modal window', () => {
|
it('renders a modal window', () => {
|
||||||
expect(wrapper.find(Modal)).toHaveLength(1);
|
setUp();
|
||||||
expect(wrapper.find(ModalHeader)).toHaveLength(1);
|
|
||||||
expect(wrapper.find(ModalBody)).toHaveLength(1);
|
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||||
expect(wrapper.find(ModalFooter)).toHaveLength(1);
|
expect(screen.getByRole('heading')).toHaveTextContent('Remove server');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays the name of the server as part of the content', () => {
|
it('displays the name of the server as part of the content', () => {
|
||||||
const modalBody = wrapper.find(ModalBody);
|
setUp();
|
||||||
|
|
||||||
expect(modalBody.find('p').first().text()).toEqual(
|
expect(screen.getByText(/^Are you sure you want to remove/)).toBeInTheDocument();
|
||||||
`Are you sure you want to remove ${serverName}?`,
|
expect(screen.getByText(serverName)).toBeInTheDocument();
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('toggles when clicking cancel button', () => {
|
it.each([
|
||||||
const cancelBtn = wrapper.find(Button).first();
|
[() => screen.getByRole('button', { name: 'Cancel' })],
|
||||||
|
[() => screen.getByLabelText('Close')],
|
||||||
|
])('toggles when clicking cancel button', async (getButton) => {
|
||||||
|
const { user } = setUp();
|
||||||
|
|
||||||
cancelBtn.simulate('click');
|
expect(toggleMock).not.toHaveBeenCalled();
|
||||||
|
await user.click(getButton());
|
||||||
|
|
||||||
expect(toggleMock).toHaveBeenCalledTimes(1);
|
expect(toggleMock).toHaveBeenCalledTimes(1);
|
||||||
expect(deleteServerMock).not.toHaveBeenCalled();
|
expect(deleteServerMock).not.toHaveBeenCalled();
|
||||||
expect(navigate).not.toHaveBeenCalled();
|
expect(navigate).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes server when clicking accept button', () => {
|
it('deletes server when clicking accept button', async () => {
|
||||||
const acceptBtn = wrapper.find(Button).last();
|
const { user } = setUp();
|
||||||
|
|
||||||
acceptBtn.simulate('click');
|
expect(toggleMock).not.toHaveBeenCalled();
|
||||||
|
expect(deleteServerMock).not.toHaveBeenCalled();
|
||||||
|
expect(navigate).not.toHaveBeenCalled();
|
||||||
|
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
||||||
|
|
||||||
expect(toggleMock).toHaveBeenCalledTimes(1);
|
expect(toggleMock).toHaveBeenCalledTimes(1);
|
||||||
expect(deleteServerMock).toHaveBeenCalledTimes(1);
|
expect(deleteServerMock).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -1,93 +1,103 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { Button } from 'reactstrap';
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import ServersExporter from '../../src/servers/services/ServersExporter';
|
import ServersExporter from '../../src/servers/services/ServersExporter';
|
||||||
import { ManageServers as createManageServers } from '../../src/servers/ManageServers';
|
import { ManageServers as createManageServers } from '../../src/servers/ManageServers';
|
||||||
import { ServersMap, ServerWithId } from '../../src/servers/data';
|
import { ServersMap, ServerWithId } from '../../src/servers/data';
|
||||||
import { SearchField } from '../../src/utils/SearchField';
|
|
||||||
import { Result } from '../../src/utils/Result';
|
|
||||||
|
|
||||||
describe('<ManageServers />', () => {
|
describe('<ManageServers />', () => {
|
||||||
const exportServers = jest.fn();
|
const exportServers = jest.fn();
|
||||||
const serversExporter = Mock.of<ServersExporter>({ exportServers });
|
const serversExporter = Mock.of<ServersExporter>({ exportServers });
|
||||||
const ImportServersBtn = () => null;
|
|
||||||
const ManageServersRow = () => null;
|
|
||||||
const useTimeoutToggle = jest.fn().mockReturnValue([false, jest.fn()]);
|
const useTimeoutToggle = jest.fn().mockReturnValue([false, jest.fn()]);
|
||||||
const ManageServers = createManageServers(serversExporter, ImportServersBtn, useTimeoutToggle, ManageServersRow);
|
const ManageServers = createManageServers(
|
||||||
let wrapper: ShallowWrapper;
|
serversExporter,
|
||||||
|
() => <span>ImportServersBtn</span>,
|
||||||
|
useTimeoutToggle,
|
||||||
|
({ hasAutoConnect }) => <span>ManageServersRow {hasAutoConnect ? '[YES]' : '[NO]'}</span>,
|
||||||
|
);
|
||||||
const createServerMock = (value: string, autoConnect = false) => Mock.of<ServerWithId>(
|
const createServerMock = (value: string, autoConnect = false) => Mock.of<ServerWithId>(
|
||||||
{ id: value, name: value, url: value, autoConnect },
|
{ id: value, name: value, url: value, autoConnect },
|
||||||
);
|
);
|
||||||
const createWrapper = (servers: ServersMap = {}) => {
|
const setUp = (servers: ServersMap = {}) => ({
|
||||||
wrapper = shallow(<ManageServers servers={servers} />);
|
user: userEvent.setup(),
|
||||||
|
...render(<MemoryRouter><ManageServers servers={servers} /></MemoryRouter>),
|
||||||
return wrapper;
|
});
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
afterEach(jest.clearAllMocks);
|
||||||
afterEach(() => wrapper?.unmount());
|
|
||||||
|
|
||||||
it('shows search field which allows searching servers, affecting te amount of rendered rows', () => {
|
it('shows search field which allows searching servers, affecting te amount of rendered rows', async () => {
|
||||||
const wrapper = createWrapper({
|
const { user } = setUp({
|
||||||
foo: createServerMock('foo'),
|
foo: createServerMock('foo'),
|
||||||
bar: createServerMock('bar'),
|
bar: createServerMock('bar'),
|
||||||
baz: createServerMock('baz'),
|
baz: createServerMock('baz'),
|
||||||
});
|
});
|
||||||
const searchField = wrapper.find(SearchField);
|
const search = async (searchTerm: string) => {
|
||||||
|
await user.clear(screen.getByPlaceholderText('Search...'));
|
||||||
|
await user.type(screen.getByPlaceholderText('Search...'), searchTerm);
|
||||||
|
};
|
||||||
|
|
||||||
expect(wrapper.find(ManageServersRow)).toHaveLength(3);
|
expect(screen.getAllByText(/^ManageServersRow/)).toHaveLength(3);
|
||||||
expect(wrapper.find('tbody').find('tr')).toHaveLength(0);
|
expect(screen.queryByText('No servers found.')).not.toBeInTheDocument();
|
||||||
|
|
||||||
searchField.simulate('change', 'foo');
|
await search('foo');
|
||||||
expect(wrapper.find(ManageServersRow)).toHaveLength(1);
|
await waitFor(() => expect(screen.getAllByText(/^ManageServersRow/)).toHaveLength(1));
|
||||||
expect(wrapper.find('tbody').find('tr')).toHaveLength(0);
|
expect(screen.queryByText('No servers found.')).not.toBeInTheDocument();
|
||||||
|
|
||||||
searchField.simulate('change', 'ba');
|
await search('ba');
|
||||||
expect(wrapper.find(ManageServersRow)).toHaveLength(2);
|
await waitFor(() => expect(screen.getAllByText(/^ManageServersRow/)).toHaveLength(2));
|
||||||
expect(wrapper.find('tbody').find('tr')).toHaveLength(0);
|
expect(screen.queryByText('No servers found.')).not.toBeInTheDocument();
|
||||||
|
|
||||||
searchField.simulate('change', 'invalid');
|
await search('invalid');
|
||||||
expect(wrapper.find(ManageServersRow)).toHaveLength(0);
|
await waitFor(() => expect(screen.queryByText(/^ManageServersRow/)).not.toBeInTheDocument());
|
||||||
expect(wrapper.find('tbody').find('tr')).toHaveLength(1);
|
expect(screen.getByText('No servers found.')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[createServerMock('foo'), 3],
|
[createServerMock('foo'), 3],
|
||||||
[createServerMock('foo', true), 4],
|
[createServerMock('foo', true), 4],
|
||||||
])('shows different amount of columns if there are at least one auto-connect server', (server, expectedCols) => {
|
])('shows different amount of columns if there are at least one auto-connect server', (server, expectedCols) => {
|
||||||
const wrapper = createWrapper({ server });
|
setUp({ server });
|
||||||
const row = wrapper.find(ManageServersRow);
|
|
||||||
|
|
||||||
expect(wrapper.find('th')).toHaveLength(expectedCols);
|
expect(screen.getAllByRole('columnheader')).toHaveLength(expectedCols);
|
||||||
expect(row.prop('hasAutoConnect')).toEqual(server.autoConnect);
|
if (server.autoConnect) {
|
||||||
|
expect(screen.getByText(/\[YES\]/)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/\[NO\]/)).not.toBeInTheDocument();
|
||||||
|
} else {
|
||||||
|
expect(screen.queryByText(/\[YES\]/)).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/\[NO\]/)).toBeInTheDocument();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[{}, 1],
|
[{}, 0],
|
||||||
[{ foo: createServerMock('foo') }, 2],
|
[{ foo: createServerMock('foo') }, 1],
|
||||||
])('shows export button if the list of servers is not empty', (servers, expectedButtons) => {
|
])('shows export button if the list of servers is not empty', (servers, expectedButtons) => {
|
||||||
const wrapper = createWrapper(servers);
|
setUp(servers);
|
||||||
const exportBtn = wrapper.find(Button);
|
expect(screen.queryAllByRole('button', { name: 'Export servers' })).toHaveLength(expectedButtons);
|
||||||
|
|
||||||
expect(exportBtn).toHaveLength(expectedButtons);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows exporting servers when clicking on button', () => {
|
it('allows exporting servers when clicking on button', async () => {
|
||||||
const wrapper = createWrapper({ foo: createServerMock('foo') });
|
const { user } = setUp({ foo: createServerMock('foo') });
|
||||||
const exportBtn = wrapper.find(Button).first();
|
|
||||||
|
|
||||||
expect(exportServers).not.toHaveBeenCalled();
|
expect(exportServers).not.toHaveBeenCalled();
|
||||||
exportBtn.simulate('click');
|
await user.click(screen.getByRole('button', { name: 'Export servers' }));
|
||||||
expect(exportServers).toHaveBeenCalled();
|
expect(exportServers).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows an error message if an error occurs while importing servers', () => {
|
it.each([[true], [false]])('shows an error message if an error occurs while importing servers', (hasError) => {
|
||||||
useTimeoutToggle.mockReturnValue([true, jest.fn()]);
|
useTimeoutToggle.mockReturnValue([hasError, jest.fn()]);
|
||||||
|
|
||||||
const wrapper = createWrapper({ foo: createServerMock('foo') });
|
setUp({ foo: createServerMock('foo') });
|
||||||
const result = wrapper.find(Result);
|
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
if (hasError) {
|
||||||
expect(result.prop('type')).toEqual('error');
|
expect(
|
||||||
|
screen.getByText('The servers could not be imported. Make sure the format is correct.'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
} else {
|
||||||
|
expect(
|
||||||
|
screen.queryByText('The servers could not be imported. Make sure the format is correct.'),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,66 +1,58 @@
|
|||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { ManageServersRow as createManageServersRow } from '../../src/servers/ManageServersRow';
|
import { ManageServersRow as createManageServersRow } from '../../src/servers/ManageServersRow';
|
||||||
import { ServerWithId } from '../../src/servers/data';
|
import { ServerWithId } from '../../src/servers/data';
|
||||||
|
|
||||||
describe('<ManageServersRow />', () => {
|
describe('<ManageServersRow />', () => {
|
||||||
const ManageServersRowDropdown = () => null;
|
const ManageServersRow = createManageServersRow(() => <span>ManageServersRowDropdown</span>);
|
||||||
const ManageServersRow = createManageServersRow(ManageServersRowDropdown);
|
|
||||||
const server: ServerWithId = {
|
const server: ServerWithId = {
|
||||||
name: 'My server',
|
name: 'My server',
|
||||||
url: 'https://example.com',
|
url: 'https://example.com',
|
||||||
apiKey: '123',
|
apiKey: '123',
|
||||||
id: 'abc',
|
id: 'abc',
|
||||||
};
|
};
|
||||||
let wrapper: ShallowWrapper;
|
const setUp = (hasAutoConnect = false, autoConnect = false) => render(
|
||||||
const createWrapper = (hasAutoConnect = false, autoConnect = false) => {
|
<MemoryRouter>
|
||||||
wrapper = shallow(<ManageServersRow server={{ ...server, autoConnect }} hasAutoConnect={hasAutoConnect} />);
|
<table>
|
||||||
|
<tbody>
|
||||||
return wrapper;
|
<ManageServersRow server={{ ...server, autoConnect }} hasAutoConnect={hasAutoConnect} />
|
||||||
};
|
</tbody>
|
||||||
|
</table>
|
||||||
afterEach(() => wrapper?.unmount());
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[true, 4],
|
[true, 4],
|
||||||
[false, 3],
|
[false, 3],
|
||||||
])('renders expected amount of columns', (hasAutoConnect, expectedCols) => {
|
])('renders expected amount of columns', (hasAutoConnect, expectedCols) => {
|
||||||
const wrapper = createWrapper(hasAutoConnect);
|
setUp(hasAutoConnect);
|
||||||
const td = wrapper.find('td');
|
|
||||||
const th = wrapper.find('th');
|
const td = screen.getAllByRole('cell');
|
||||||
|
const th = screen.getAllByRole('columnheader');
|
||||||
|
|
||||||
expect(td.length + th.length).toEqual(expectedCols);
|
expect(td.length + th.length).toEqual(expectedCols);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a dropdown', () => {
|
it('renders a dropdown', () => {
|
||||||
const wrapper = createWrapper();
|
setUp();
|
||||||
const dropdown = wrapper.find(ManageServersRowDropdown);
|
expect(screen.getByText('ManageServersRowDropdown')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(dropdown).toHaveLength(1);
|
|
||||||
expect(dropdown.prop('server')).toEqual(expect.objectContaining(server));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[true, 1],
|
[true],
|
||||||
[false, 0],
|
[false],
|
||||||
])('renders auto-connect icon only if server is autoConnect', (autoConnect, expectedIcons) => {
|
])('renders auto-connect icon only if server is autoConnect', (autoConnect) => {
|
||||||
const wrapper = createWrapper(true, autoConnect);
|
const { container } = setUp(true, autoConnect);
|
||||||
const icon = wrapper.find(FontAwesomeIcon);
|
expect(container).toMatchSnapshot();
|
||||||
const iconTooltip = wrapper.find(UncontrolledTooltip);
|
|
||||||
|
|
||||||
expect(icon).toHaveLength(expectedIcons);
|
|
||||||
expect(iconTooltip).toHaveLength(expectedIcons);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders server props where appropriate', () => {
|
it('renders server props where appropriate', () => {
|
||||||
const wrapper = createWrapper();
|
setUp();
|
||||||
const link = wrapper.find(Link);
|
|
||||||
const td = wrapper.find('td').first();
|
|
||||||
|
|
||||||
expect(link.prop('to')).toEqual(`/server/${server.id}`);
|
const link = screen.getByRole('link');
|
||||||
expect(link.prop('children')).toEqual(server.name);
|
|
||||||
expect(td.prop('children')).toEqual(server.url);
|
expect(link).toHaveAttribute('href', `/server/${server.id}`);
|
||||||
|
expect(link).toHaveTextContent(server.name);
|
||||||
|
expect(screen.getByText(server.url)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,84 +1,60 @@
|
|||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { DropdownItem } from 'reactstrap';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { ServerWithId } from '../../src/servers/data';
|
import { ServerWithId } from '../../src/servers/data';
|
||||||
import { ManageServersRowDropdown as createManageServersRowDropdown } from '../../src/servers/ManageServersRowDropdown';
|
import { ManageServersRowDropdown as createManageServersRowDropdown } from '../../src/servers/ManageServersRowDropdown';
|
||||||
|
|
||||||
describe('<ManageServersRowDropdown />', () => {
|
describe('<ManageServersRowDropdown />', () => {
|
||||||
const DeleteServerModal = () => null;
|
const ManageServersRowDropdown = createManageServersRowDropdown(
|
||||||
const ManageServersRowDropdown = createManageServersRowDropdown(DeleteServerModal);
|
({ isOpen }) => <span>DeleteServerModal {isOpen ? '[OPEN]' : '[CLOSED]'}</span>,
|
||||||
|
);
|
||||||
const setAutoConnect = jest.fn();
|
const setAutoConnect = jest.fn();
|
||||||
let wrapper: ShallowWrapper;
|
const setUp = (autoConnect = false) => {
|
||||||
const createWrapper = (autoConnect = false) => {
|
|
||||||
const server = Mock.of<ServerWithId>({ id: 'abc123', autoConnect });
|
const server = Mock.of<ServerWithId>({ id: 'abc123', autoConnect });
|
||||||
|
return {
|
||||||
wrapper = shallow(<ManageServersRowDropdown setAutoConnect={setAutoConnect} server={server} />);
|
user: userEvent.setup(),
|
||||||
|
...render(
|
||||||
return wrapper;
|
<MemoryRouter>
|
||||||
|
<ManageServersRowDropdown setAutoConnect={setAutoConnect} server={server} />
|
||||||
|
</MemoryRouter>,
|
||||||
|
),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
afterEach(jest.clearAllMocks);
|
||||||
|
|
||||||
it('renders expected amount of dropdown items', () => {
|
it('renders expected amount of dropdown items', async () => {
|
||||||
const wrapper = createWrapper();
|
const { user } = setUp();
|
||||||
const items = wrapper.find(DropdownItem);
|
|
||||||
|
|
||||||
expect(items).toHaveLength(5);
|
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
|
||||||
expect(items.find('[divider]')).toHaveLength(1);
|
await user.click(screen.getByRole('button'));
|
||||||
expect(items.at(0).prop('to')).toEqual('/server/abc123');
|
expect(screen.getByRole('menu')).toBeInTheDocument();
|
||||||
expect(items.at(1).prop('to')).toEqual('/server/abc123/edit');
|
|
||||||
|
expect(screen.getAllByRole('menuitem')).toHaveLength(4);
|
||||||
|
expect(screen.getByRole('menuitem', { name: 'Connect' })).toHaveAttribute('href', '/server/abc123');
|
||||||
|
expect(screen.getByRole('menuitem', { name: 'Edit server' })).toHaveAttribute('href', '/server/abc123/edit');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows toggling auto-connect', () => {
|
it('allows toggling auto-connect', async () => {
|
||||||
const wrapper = createWrapper();
|
const { user } = setUp();
|
||||||
|
|
||||||
expect(setAutoConnect).not.toHaveBeenCalled();
|
expect(setAutoConnect).not.toHaveBeenCalled();
|
||||||
wrapper.find(DropdownItem).at(2).simulate('click');
|
await user.click(screen.getByRole('button'));
|
||||||
|
await user.click(screen.getByRole('menuitem', { name: 'Auto-connect' }));
|
||||||
expect(setAutoConnect).toHaveBeenCalledWith(expect.objectContaining({ id: 'abc123' }), true);
|
expect(setAutoConnect).toHaveBeenCalledWith(expect.objectContaining({ id: 'abc123' }), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a modal', () => {
|
it('renders deletion modal', async () => {
|
||||||
const wrapper = createWrapper();
|
const { user } = setUp();
|
||||||
const modal = wrapper.find(DeleteServerModal);
|
|
||||||
|
|
||||||
expect(modal).toHaveLength(1);
|
expect(screen.queryByText('DeleteServerModal [OPEN]')).not.toBeInTheDocument();
|
||||||
expect(modal.prop('redirectHome')).toEqual(false);
|
expect(screen.getByText('DeleteServerModal [CLOSED]')).toBeInTheDocument();
|
||||||
expect(modal.prop('server')).toEqual(expect.objectContaining({ id: 'abc123' }));
|
|
||||||
expect(modal.prop('isOpen')).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('allows toggling the modal', () => {
|
await user.click(screen.getByRole('button'));
|
||||||
const wrapper = createWrapper();
|
await user.click(screen.getByRole('menuitem', { name: 'Remove server' }));
|
||||||
const modalToggle = wrapper.find(DropdownItem).last();
|
|
||||||
|
|
||||||
expect(wrapper.find(DeleteServerModal).prop('isOpen')).toEqual(false);
|
expect(screen.getByText('DeleteServerModal [OPEN]')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('DeleteServerModal [CLOSED]')).not.toBeInTheDocument();
|
||||||
modalToggle.simulate('click');
|
|
||||||
expect(wrapper.find(DeleteServerModal).prop('isOpen')).toEqual(true);
|
|
||||||
|
|
||||||
(wrapper.find(DeleteServerModal).prop('toggle') as Function)();
|
|
||||||
expect(wrapper.find(DeleteServerModal).prop('isOpen')).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can be toggled', () => {
|
|
||||||
const wrapper = createWrapper();
|
|
||||||
|
|
||||||
expect(wrapper.prop('isOpen')).toEqual(false);
|
|
||||||
|
|
||||||
(wrapper.prop('toggle') as Function)();
|
|
||||||
expect(wrapper.prop('isOpen')).toEqual(true);
|
|
||||||
|
|
||||||
(wrapper.prop('toggle') as Function)();
|
|
||||||
expect(wrapper.prop('isOpen')).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[true, 'Do not auto-connect'],
|
|
||||||
[false, 'Auto-connect'],
|
|
||||||
])('shows different auto-connect toggle text depending on current server status', (autoConnect, expectedText) => {
|
|
||||||
const wrapper = createWrapper(autoConnect);
|
|
||||||
const item = wrapper.find(DropdownItem).at(2);
|
|
||||||
|
|
||||||
expect(item.html()).toContain(expectedText);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { ListGroup } from 'reactstrap';
|
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { ServersListGroup } from '../../src/servers/ServersListGroup';
|
import { ServersListGroup } from '../../src/servers/ServersListGroup';
|
||||||
import { ServerWithId } from '../../src/servers/data';
|
import { ServerWithId } from '../../src/servers/data';
|
||||||
|
|
||||||
@@ -9,44 +9,36 @@ describe('<ServersListGroup />', () => {
|
|||||||
Mock.of<ServerWithId>({ name: 'foo', id: '123' }),
|
Mock.of<ServerWithId>({ name: 'foo', id: '123' }),
|
||||||
Mock.of<ServerWithId>({ name: 'bar', id: '456' }),
|
Mock.of<ServerWithId>({ name: 'bar', id: '456' }),
|
||||||
];
|
];
|
||||||
let wrapped: ShallowWrapper;
|
const setUp = (params: { servers?: ServerWithId[]; withChildren?: boolean; embedded?: boolean }) => {
|
||||||
const createComponent = (params: { servers?: ServerWithId[]; withChildren?: boolean; embedded?: boolean }) => {
|
|
||||||
const { servers = [], withChildren = true, embedded } = params;
|
const { servers = [], withChildren = true, embedded } = params;
|
||||||
|
|
||||||
wrapped = shallow(
|
return render(
|
||||||
<ServersListGroup servers={servers} embedded={embedded}>
|
<MemoryRouter>
|
||||||
{withChildren ? 'The list of servers' : undefined}
|
<ServersListGroup servers={servers} embedded={embedded}>
|
||||||
</ServersListGroup>,
|
{withChildren ? 'The list of servers' : undefined}
|
||||||
|
</ServersListGroup>
|
||||||
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
return wrapped;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(() => wrapped?.unmount());
|
|
||||||
|
|
||||||
it('renders title', () => {
|
it('renders title', () => {
|
||||||
const wrapped = createComponent({});
|
setUp({});
|
||||||
const title = wrapped.find('h5');
|
expect(screen.getByRole('heading')).toHaveTextContent('The list of servers');
|
||||||
|
|
||||||
expect(title).toHaveLength(1);
|
|
||||||
expect(title.text()).toEqual('The list of servers');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not render title when children is not provided', () => {
|
it('does not render title when children is not provided', () => {
|
||||||
const wrapped = createComponent({ withChildren: false });
|
setUp({ withChildren: false });
|
||||||
const title = wrapped.find('h5');
|
expect(screen.queryByRole('heading')).not.toBeInTheDocument();
|
||||||
|
|
||||||
expect(title).toHaveLength(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[servers],
|
[servers],
|
||||||
[[]],
|
[[]],
|
||||||
])('shows servers list', (servers) => {
|
])('shows servers list', (servers) => {
|
||||||
const wrapped = createComponent({ servers });
|
setUp({ servers });
|
||||||
|
|
||||||
expect(wrapped.find(ListGroup)).toHaveLength(servers.length ? 1 : 0);
|
expect(screen.queryAllByRole('list')).toHaveLength(servers.length ? 1 : 0);
|
||||||
expect(wrapped.find('ServerListItem')).toHaveLength(servers.length);
|
expect(screen.queryAllByRole('link')).toHaveLength(servers.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@@ -54,9 +46,7 @@ describe('<ServersListGroup />', () => {
|
|||||||
[false, 'servers-list__list-group'],
|
[false, 'servers-list__list-group'],
|
||||||
[undefined, 'servers-list__list-group'],
|
[undefined, 'servers-list__list-group'],
|
||||||
])('renders proper classes for embedded', (embedded, expectedClasses) => {
|
])('renders proper classes for embedded', (embedded, expectedClasses) => {
|
||||||
const wrapped = createComponent({ servers, embedded });
|
setUp({ servers, embedded });
|
||||||
const listGroup = wrapped.find(ListGroup);
|
expect(screen.getByRole('list')).toHaveAttribute('class', `${expectedClasses} list-group`);
|
||||||
|
|
||||||
expect(listGroup.prop('className')).toEqual(expectedClasses);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
98
test/servers/__snapshots__/ManageServersRow.test.tsx.snap
Normal file
98
test/servers/__snapshots__/ManageServersRow.test.tsx.snap
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<ManageServersRow /> renders auto-connect icon only if server is autoConnect 1`] = `
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
class="responsive-table__row"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="responsive-table__cell"
|
||||||
|
data-th="Auto-connect"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="svg-inline--fa fa-check text-primary"
|
||||||
|
data-icon="check"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
id="autoConnectIcon"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 448 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M438.6 105.4C451.1 117.9 451.1 138.1 438.6 150.6L182.6 406.6C170.1 419.1 149.9 419.1 137.4 406.6L9.372 278.6C-3.124 266.1-3.124 245.9 9.372 233.4C21.87 220.9 42.13 220.9 54.63 233.4L159.1 338.7L393.4 105.4C405.9 92.88 426.1 92.88 438.6 105.4H438.6z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</td>
|
||||||
|
<th
|
||||||
|
class="responsive-table__cell"
|
||||||
|
data-th="Name"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/server/abc"
|
||||||
|
>
|
||||||
|
My server
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
class="responsive-table__cell"
|
||||||
|
data-th="Base URL"
|
||||||
|
>
|
||||||
|
https://example.com
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="responsive-table__cell text-end"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
ManageServersRowDropdown
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<ManageServersRow /> renders auto-connect icon only if server is autoConnect 2`] = `
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
class="responsive-table__row"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="responsive-table__cell"
|
||||||
|
data-th="Auto-connect"
|
||||||
|
/>
|
||||||
|
<th
|
||||||
|
class="responsive-table__cell"
|
||||||
|
data-th="Name"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/server/abc"
|
||||||
|
>
|
||||||
|
My server
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
class="responsive-table__cell"
|
||||||
|
data-th="Base URL"
|
||||||
|
>
|
||||||
|
https://example.com
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="responsive-table__cell text-end"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
ManageServersRowDropdown
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -1,30 +1,22 @@
|
|||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { render } from '@testing-library/react';
|
||||||
import { DateProps, Time } from '../../src/utils/Time';
|
import { TimeProps, Time } from '../../src/utils/Time';
|
||||||
import { parseDate } from '../../src/utils/helpers/date';
|
import { parseDate } from '../../src/utils/helpers/date';
|
||||||
|
|
||||||
describe('<Time />', () => {
|
describe('<Time />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
const setUp = (props: TimeProps) => render(<Time {...props} />);
|
||||||
const createWrapper = (props: DateProps) => {
|
|
||||||
wrapper = shallow(<Time {...props} />);
|
|
||||||
|
|
||||||
return wrapper;
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => wrapper?.unmount());
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[{ date: parseDate('2020-05-05', 'yyyy-MM-dd') }, '1588636800000', '2020-05-05 00:00'],
|
[{ date: parseDate('2020-05-05', 'yyyy-MM-dd') }, '1588636800000', '2020-05-05 00:00'],
|
||||||
[{ date: parseDate('2021-03-20', 'yyyy-MM-dd'), format: 'dd/MM/yyyy' }, '1616198400000', '20/03/2021'],
|
[{ date: parseDate('2021-03-20', 'yyyy-MM-dd'), format: 'dd/MM/yyyy' }, '1616198400000', '20/03/2021'],
|
||||||
])('includes expected dateTime and format', (props, expectedDateTime, expectedFormatted) => {
|
])('includes expected dateTime and format', (props, expectedDateTime, expectedFormatted) => {
|
||||||
const wrapper = createWrapper(props);
|
const { container } = setUp(props);
|
||||||
|
|
||||||
expect(wrapper.prop('dateTime')).toEqual(expectedDateTime);
|
expect(container.firstChild).toHaveAttribute('datetime', expectedDateTime);
|
||||||
expect(wrapper.prop('children')).toEqual(expectedFormatted);
|
expect(container.firstChild).toHaveTextContent(expectedFormatted);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders relative times when requested', () => {
|
it('renders relative times when requested', () => {
|
||||||
const wrapper = createWrapper({ date: new Date(), relative: true });
|
const { container } = setUp({ date: new Date(), relative: true });
|
||||||
|
expect(container.firstChild).toHaveTextContent(' ago');
|
||||||
expect(wrapper.prop('children')).toContain(' ago');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user