mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-02-19 08:16:37 +00:00
Do not inject servers state or actions
This commit is contained in:
@@ -19,10 +19,11 @@ export const renderWithStore = (
|
||||
element: ReactElement,
|
||||
{ initialState = {}, ...options }: RenderOptionsWithState = {},
|
||||
) => {
|
||||
const Wrapper = ({ children }: PropsWithChildren) => (
|
||||
<Provider store={setUpStore(initialState)}>
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
return renderWithEvents(element, { ...options, wrapper: Wrapper });
|
||||
const store = setUpStore(initialState);
|
||||
const Wrapper = ({ children }: PropsWithChildren) => <Provider store={store}>{children}</Provider>;
|
||||
|
||||
return {
|
||||
store,
|
||||
...renderWithEvents(element, { ...options, wrapper: Wrapper }),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,19 +2,17 @@ import { act, screen } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { AppFactory } from '../../src/app/App';
|
||||
import type { ServerWithId } from '../../src/servers/data';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
describe('<App />', () => {
|
||||
const App = AppFactory(
|
||||
fromPartial({
|
||||
MainHeader: () => <>MainHeader</>,
|
||||
Home: () => <>Home</>,
|
||||
ShlinkWebComponentContainer: () => <>ShlinkWebComponentContainer</>,
|
||||
CreateServer: () => <>CreateServer</>,
|
||||
EditServer: () => <>EditServer</>,
|
||||
ManageServers: () => <>ManageServers</>,
|
||||
ShlinkVersionsContainer: () => <>ShlinkVersions</>,
|
||||
}),
|
||||
);
|
||||
const setUp = async (activeRoute = '/') => act(() => renderWithStore(
|
||||
@@ -27,24 +25,25 @@ describe('<App />', () => {
|
||||
resetAppUpdate={() => {}}
|
||||
/>
|
||||
</MemoryRouter>,
|
||||
{
|
||||
initialState: {
|
||||
servers: {
|
||||
abc123: fromPartial<ServerWithId>({ id: 'abc123', name: 'abc123 server' }),
|
||||
def456: fromPartial<ServerWithId>({ id: 'def456', name: 'def456 server' }),
|
||||
},
|
||||
},
|
||||
},
|
||||
));
|
||||
|
||||
it('passes a11y checks', () => checkAccessibility(setUp()));
|
||||
|
||||
it('renders children components', async () => {
|
||||
await setUp();
|
||||
|
||||
expect(screen.getByText('MainHeader')).toBeInTheDocument();
|
||||
expect(screen.getByText('ShlinkVersions')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['/settings/general', 'User interface'],
|
||||
['/settings/short-urls', 'Short URLs form'],
|
||||
['/manage-servers', 'ManageServers'],
|
||||
['/server/create', 'CreateServer'],
|
||||
['/server/abc123/edit', 'EditServer'],
|
||||
['/server/def456/edit', 'EditServer'],
|
||||
['/server/abc123/edit', 'Edit "abc123 server"'],
|
||||
['/server/def456/edit', 'Edit "def456 server"'],
|
||||
['/server/abc123/foo', 'ShlinkWebComponentContainer'],
|
||||
['/server/def456/bar', 'ShlinkWebComponentContainer'],
|
||||
['/other', 'Oops! We could not find requested route.'],
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { Home } from '../../src/common/Home';
|
||||
import type { ServersMap, ServerWithId } from '../../src/servers/data';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
describe('<Home />', () => {
|
||||
const setUp = (servers: ServersMap = {}) => render(
|
||||
const setUp = (servers: ServersMap = {}) => renderWithStore(
|
||||
<MemoryRouter>
|
||||
<Home servers={servers} />
|
||||
<Home />
|
||||
</MemoryRouter>,
|
||||
{
|
||||
initialState: { servers },
|
||||
},
|
||||
);
|
||||
|
||||
it('passes a11y checks', () => checkAccessibility(
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { Router } from 'react-router';
|
||||
import { MainHeaderFactory } from '../../src/common/MainHeader';
|
||||
import { MainHeader } from '../../src/common/MainHeader';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
describe('<MainHeader />', () => {
|
||||
const MainHeader = MainHeaderFactory(fromPartial({
|
||||
// Fake this component as a li[role="menuitem"], as it gets rendered inside a ul[role="menu"]
|
||||
ServersDropdown: () => <li role="menuitem">ServersDropdown</li>,
|
||||
}));
|
||||
const setUp = (pathname = '') => {
|
||||
const history = createMemoryHistory();
|
||||
history.push(pathname);
|
||||
|
||||
return renderWithEvents(
|
||||
return renderWithStore(
|
||||
<Router location={history.location} navigator={history}>
|
||||
<MainHeader />
|
||||
</Router>,
|
||||
@@ -26,7 +21,7 @@ describe('<MainHeader />', () => {
|
||||
|
||||
it('renders ServersDropdown', () => {
|
||||
setUp();
|
||||
expect(screen.getByText('ServersDropdown')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Servers' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { ShlinkWebComponentContainerFactory } from '../../src/common/ShlinkWebComponentContainer';
|
||||
import type { NonReachableServer, NotFoundServer, SelectedServer } from '../../src/servers/data';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
@@ -15,12 +16,13 @@ describe('<ShlinkWebComponentContainer />', () => {
|
||||
const ShlinkWebComponentContainer = ShlinkWebComponentContainerFactory(fromPartial({
|
||||
buildShlinkApiClient: vi.fn().mockReturnValue(fromPartial({})),
|
||||
TagColorsStorage: fromPartial({}),
|
||||
ServerError: () => <>ServerError</>,
|
||||
}));
|
||||
const setUp = (selectedServer: SelectedServer) => renderWithStore(
|
||||
<ShlinkWebComponentContainer settings={{}} />,
|
||||
<MemoryRouter>
|
||||
<ShlinkWebComponentContainer settings={{}} />
|
||||
</MemoryRouter>,
|
||||
{
|
||||
initialState: { selectedServer },
|
||||
initialState: { selectedServer, servers: {} },
|
||||
},
|
||||
);
|
||||
|
||||
@@ -30,18 +32,20 @@ describe('<ShlinkWebComponentContainer />', () => {
|
||||
setUp(null);
|
||||
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
expect(screen.queryByText('ServerError')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('ShlinkWebComponent')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[fromPartial<NotFoundServer>({ serverNotFound: true })],
|
||||
[fromPartial<NonReachableServer>({ serverNotReachable: true })],
|
||||
])('shows error for non reachable servers', (selectedServer) => {
|
||||
[fromPartial<NotFoundServer>({ serverNotFound: true }), 'Could not find this Shlink server.'],
|
||||
[
|
||||
fromPartial<NonReachableServer>({ id: 'foo', serverNotReachable: true }),
|
||||
/Could not connect to this Shlink server/,
|
||||
],
|
||||
])('shows error for non reachable servers', (selectedServer, expectedError) => {
|
||||
setUp(selectedServer);
|
||||
|
||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('ServerError')).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedError)).toBeInTheDocument();
|
||||
expect(screen.queryByText('ShlinkWebComponent')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -49,7 +53,6 @@ describe('<ShlinkWebComponentContainer />', () => {
|
||||
setUp(fromPartial({ version: '3.0.0' }));
|
||||
|
||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('ServerError')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('ShlinkWebComponent')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Router } from 'react-router';
|
||||
import { CreateServerFactory } from '../../src/servers/CreateServer';
|
||||
import type { ServersMap } from '../../src/servers/data';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
type SetUpOptions = {
|
||||
serversImported?: boolean;
|
||||
@@ -14,9 +14,8 @@ type SetUpOptions = {
|
||||
};
|
||||
|
||||
describe('<CreateServer />', () => {
|
||||
const createServersMock = vi.fn();
|
||||
const defaultServers: ServersMap = {
|
||||
foo: fromPartial({ url: 'https://existing_url.com', apiKey: 'existing_api_key' }),
|
||||
foo: fromPartial({ url: 'https://existing_url.com', apiKey: 'existing_api_key', id: 'foo' }),
|
||||
};
|
||||
const setUp = ({ serversImported = false, importFailed = false, servers = defaultServers }: SetUpOptions = {}) => {
|
||||
let callCount = 0;
|
||||
@@ -33,10 +32,13 @@ describe('<CreateServer />', () => {
|
||||
|
||||
return {
|
||||
history,
|
||||
...renderWithEvents(
|
||||
...renderWithStore(
|
||||
<Router location={history.location} navigator={history}>
|
||||
<CreateServer createServers={createServersMock} servers={servers} />
|
||||
<CreateServer />
|
||||
</Router>,
|
||||
{
|
||||
initialState: { servers },
|
||||
},
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -68,21 +70,23 @@ describe('<CreateServer />', () => {
|
||||
});
|
||||
|
||||
it('creates server data when form is submitted', async () => {
|
||||
const { user, history } = setUp();
|
||||
|
||||
expect(createServersMock).not.toHaveBeenCalled();
|
||||
const { user, history, store } = setUp();
|
||||
const expectedServerId = 'the_name-the_url.com';
|
||||
|
||||
await user.type(screen.getByLabelText(/^Name/), 'the_name');
|
||||
await user.type(screen.getByLabelText(/^URL/), 'https://the_url.com');
|
||||
await user.type(screen.getByLabelText(/^API key/), 'the_api_key');
|
||||
fireEvent.submit(screen.getByRole('form'));
|
||||
|
||||
expect(createServersMock).toHaveBeenCalledWith([expect.objectContaining({
|
||||
expect(store.getState().servers[expectedServerId]).not.toBeDefined();
|
||||
fireEvent.submit(screen.getByRole('form'));
|
||||
expect(store.getState().servers[expectedServerId]).toEqual(expect.objectContaining({
|
||||
id: expectedServerId,
|
||||
name: 'the_name',
|
||||
url: 'https://the_url.com',
|
||||
apiKey: 'the_api_key',
|
||||
})]);
|
||||
expect(history.location.pathname).toEqual(expect.stringMatching(/^\/server\//));
|
||||
}));
|
||||
|
||||
expect(history.location.pathname).toEqual(`/server/${expectedServerId}`);
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -92,12 +96,12 @@ describe('<CreateServer />', () => {
|
||||
await user.type(screen.getByLabelText(/^Name/), 'the_name');
|
||||
await user.type(screen.getByLabelText(/^URL/), 'https://existing_url.com');
|
||||
await user.type(screen.getByLabelText(/^API key/), 'existing_api_key');
|
||||
|
||||
fireEvent.submit(screen.getByRole('form'));
|
||||
|
||||
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
|
||||
await user.click(screen.getByRole('button', { name: 'Discard' }));
|
||||
|
||||
expect(createServersMock).not.toHaveBeenCalled();
|
||||
expect(history.location.pathname).toEqual('/foo'); // Goes back to first route from history's initialEntries
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,19 +3,14 @@ import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Router } from 'react-router';
|
||||
import { DeleteServerButtonFactory } from '../../src/servers/DeleteServerButton';
|
||||
import type { DeleteServerModalProps } from '../../src/servers/DeleteServerModal';
|
||||
import { DeleteServerModal } from '../../src/servers/DeleteServerModal';
|
||||
import { DeleteServerButton } from '../../src/servers/DeleteServerButton';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
describe('<DeleteServerButton />', () => {
|
||||
const DeleteServerButton = DeleteServerButtonFactory(fromPartial({
|
||||
DeleteServerModal: (props: DeleteServerModalProps) => <DeleteServerModal {...props} deleteServer={vi.fn()} />,
|
||||
}));
|
||||
const setUp = (children: ReactNode = 'Remove this server') => {
|
||||
const history = createMemoryHistory({ initialEntries: ['/foo'] });
|
||||
const result = renderWithEvents(
|
||||
const result = renderWithStore(
|
||||
<Router location={history.location} navigator={history}>
|
||||
<DeleteServerButton server={fromPartial({})}>{children}</DeleteServerButton>
|
||||
</Router>,
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import type { ServerWithId } from '../../src/servers/data';
|
||||
import { DeleteServerModal } from '../../src/servers/DeleteServerModal';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
import { TestModalWrapper } from '../__helpers__/TestModalWrapper';
|
||||
|
||||
describe('<DeleteServerModal />', () => {
|
||||
const deleteServerMock = vi.fn();
|
||||
const serverName = 'the_server_name';
|
||||
const setUp = () => renderWithEvents(
|
||||
const server = fromPartial<ServerWithId>({ id: 'foo', name: serverName });
|
||||
const setUp = () => renderWithStore(
|
||||
<TestModalWrapper
|
||||
renderModal={(args) => (
|
||||
<DeleteServerModal
|
||||
{...args}
|
||||
server={fromPartial({ name: serverName })}
|
||||
deleteServer={deleteServerMock}
|
||||
/>
|
||||
)}
|
||||
renderModal={(args) => <DeleteServerModal {...args} server={server} />}
|
||||
/>,
|
||||
{
|
||||
initialState: {
|
||||
servers: { foo: server },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
it('passes a11y checks', () => checkAccessibility(setUp()));
|
||||
@@ -40,19 +40,21 @@ describe('<DeleteServerModal />', () => {
|
||||
[() => screen.getByRole('button', { name: 'Cancel' })],
|
||||
[() => screen.getByLabelText('Close dialog')],
|
||||
])('closes dialog when clicking cancel button', async (getButton) => {
|
||||
const { user } = setUp();
|
||||
const { user, store } = setUp();
|
||||
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
await user.click(getButton());
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(deleteServerMock).not.toHaveBeenCalled();
|
||||
|
||||
// No server has been deleted
|
||||
expect(Object.keys(store.getState().servers)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('deletes server when clicking accept button', async () => {
|
||||
const { user } = setUp();
|
||||
const { user, store } = setUp();
|
||||
|
||||
expect(deleteServerMock).not.toHaveBeenCalled();
|
||||
expect(Object.keys(store.getState().servers)).toHaveLength(1);
|
||||
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
||||
expect(deleteServerMock).toHaveBeenCalledOnce();
|
||||
expect(Object.keys(store.getState().servers)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { Router } from 'react-router';
|
||||
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
|
||||
import { EditServerFactory } from '../../src/servers/EditServer';
|
||||
import { isServerWithId } from '../../src/servers/data';
|
||||
import { EditServer } from '../../src/servers/EditServer';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
describe('<EditServer />', () => {
|
||||
const ServerError = vi.fn();
|
||||
const editServerMock = vi.fn();
|
||||
const defaultSelectedServer = fromPartial<ReachableServer>({
|
||||
id: 'abc123',
|
||||
name: 'the_name',
|
||||
url: 'the_url',
|
||||
apiKey: 'the_api_key',
|
||||
});
|
||||
const EditServer = EditServerFactory(fromPartial({ ServerError }));
|
||||
const setUp = (selectedServer: SelectedServer = defaultSelectedServer) => {
|
||||
const history = createMemoryHistory({ initialEntries: ['/foo', '/bar'] });
|
||||
return {
|
||||
history,
|
||||
...renderWithStore(
|
||||
<Router location={history.location} navigator={history}>
|
||||
<EditServer editServer={editServerMock} />
|
||||
<EditServer />
|
||||
</Router>,
|
||||
{
|
||||
initialState: { selectedServer },
|
||||
initialState: {
|
||||
selectedServer,
|
||||
servers: isServerWithId(selectedServer) ? { [selectedServer.id]: selectedServer } : {},
|
||||
},
|
||||
},
|
||||
),
|
||||
};
|
||||
@@ -56,7 +57,7 @@ describe('<EditServer />', () => {
|
||||
});
|
||||
|
||||
it('edits server and redirects to it when form is submitted', async () => {
|
||||
const { user, history } = setUp();
|
||||
const { user, history, store } = setUp();
|
||||
|
||||
await user.type(screen.getByLabelText(/^Name/), ' edited');
|
||||
await user.type(screen.getByLabelText(/^URL/), ' edited');
|
||||
@@ -64,12 +65,10 @@ describe('<EditServer />', () => {
|
||||
// await user.click(screen.getByRole('button', { name: 'Save' }));
|
||||
fireEvent.submit(screen.getByRole('form'));
|
||||
|
||||
expect(editServerMock).toHaveBeenCalledWith('abc123', {
|
||||
expect(store.getState().servers[defaultSelectedServer.id]).toEqual(expect.objectContaining({
|
||||
name: 'the_name edited',
|
||||
url: 'the_url edited',
|
||||
apiKey: 'the_api_key',
|
||||
forwardCredentials: false,
|
||||
});
|
||||
}));
|
||||
|
||||
// After saving we go back, to the first route from history's initialEntries
|
||||
expect(history.location.pathname).toEqual('/foo');
|
||||
@@ -78,16 +77,15 @@ describe('<EditServer />', () => {
|
||||
it.each([
|
||||
{ forwardCredentials: true },
|
||||
{ forwardCredentials: false },
|
||||
])('edits advanced options - forward credentials', async (serverPartial) => {
|
||||
const { user } = setUp({ ...defaultSelectedServer, ...serverPartial });
|
||||
])('edits advanced options - forward credentials', async ({ forwardCredentials }) => {
|
||||
const { user, store } = setUp({ ...defaultSelectedServer, forwardCredentials });
|
||||
|
||||
await user.click(screen.getByText('Advanced options'));
|
||||
await user.click(screen.getByLabelText('Forward credentials to this server on every request.'));
|
||||
|
||||
fireEvent.submit(screen.getByRole('form'));
|
||||
|
||||
expect(editServerMock).toHaveBeenCalledWith('abc123', expect.objectContaining({
|
||||
forwardCredentials: !serverPartial.forwardCredentials,
|
||||
}));
|
||||
await waitFor(() => expect(store.getState().servers[defaultSelectedServer.id]).toEqual(expect.objectContaining({
|
||||
forwardCredentials: !forwardCredentials,
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { ServersMap, ServerWithId } from '../../src/servers/data';
|
||||
import { ManageServersFactory } from '../../src/servers/ManageServers';
|
||||
import type { ServersExporter } from '../../src/servers/services/ServersExporter';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
describe('<ManageServers />', () => {
|
||||
const exportServers = vi.fn();
|
||||
@@ -15,15 +15,15 @@ describe('<ManageServers />', () => {
|
||||
ServersExporter: serversExporter,
|
||||
ImportServersBtn: () => <span>ImportServersBtn</span>,
|
||||
useTimeoutToggle,
|
||||
ManageServersRow: ({ hasAutoConnect }: { hasAutoConnect: boolean }) => (
|
||||
<tr><td>ManageServersRow {hasAutoConnect ? '[YES]' : '[NO]'}</td></tr>
|
||||
),
|
||||
}));
|
||||
const createServerMock = (value: string, autoConnect = false) => fromPartial<ServerWithId>(
|
||||
{ id: value, name: value, url: value, autoConnect },
|
||||
);
|
||||
const setUp = (servers: ServersMap = {}) => renderWithEvents(
|
||||
<MemoryRouter><ManageServers servers={servers} /></MemoryRouter>,
|
||||
const setUp = (servers: ServersMap = {}) => renderWithStore(
|
||||
<MemoryRouter><ManageServers /></MemoryRouter>,
|
||||
{
|
||||
initialState: { servers },
|
||||
},
|
||||
);
|
||||
|
||||
it('passes a11y checks', () => checkAccessibility(setUp({
|
||||
@@ -42,20 +42,22 @@ describe('<ManageServers />', () => {
|
||||
await user.clear(screen.getByPlaceholderText('Search...'));
|
||||
await user.type(screen.getByPlaceholderText('Search...'), searchTerm);
|
||||
};
|
||||
// Add one for the header row
|
||||
const expectRows = (amount: number) => expect(screen.getAllByRole('row')).toHaveLength(amount + 1);
|
||||
|
||||
expect(screen.getAllByText(/^ManageServersRow/)).toHaveLength(3);
|
||||
expectRows(3);
|
||||
expect(screen.queryByText('No servers found.')).not.toBeInTheDocument();
|
||||
|
||||
await search('foo');
|
||||
await waitFor(() => expect(screen.getAllByText(/^ManageServersRow/)).toHaveLength(1));
|
||||
await waitFor(() => expectRows(1));
|
||||
expect(screen.queryByText('No servers found.')).not.toBeInTheDocument();
|
||||
|
||||
await search('Ba');
|
||||
await waitFor(() => expect(screen.getAllByText(/^ManageServersRow/)).toHaveLength(2));
|
||||
await waitFor(() => expectRows(2));
|
||||
expect(screen.queryByText('No servers found.')).not.toBeInTheDocument();
|
||||
|
||||
await search('invalid');
|
||||
await waitFor(() => expect(screen.queryByText(/^ManageServersRow/)).not.toBeInTheDocument());
|
||||
await waitFor(() => expectRows(1));
|
||||
expect(screen.getByText('No servers found.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -67,11 +69,9 @@ describe('<ManageServers />', () => {
|
||||
|
||||
expect(screen.getAllByRole('columnheader')).toHaveLength(expectedCols);
|
||||
if (server.autoConnect) {
|
||||
expect(screen.getByText(/\[YES]/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/\[NO]/)).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('auto-connect')).toBeInTheDocument();
|
||||
} else {
|
||||
expect(screen.queryByText(/\[YES]/)).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/\[NO]/)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('auto-connect')).not.toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import { Table } from '@shlinkio/shlink-frontend-kit';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import type { ServerWithId } from '../../src/servers/data';
|
||||
import { ManageServersRowFactory } from '../../src/servers/ManageServersRow';
|
||||
import { ManageServersRow } from '../../src/servers/ManageServersRow';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
describe('<ManageServersRow />', () => {
|
||||
const ManageServersRow = ManageServersRowFactory(fromPartial({
|
||||
ManageServersRowDropdown: () => <span>ManageServersRowDropdown</span>,
|
||||
}));
|
||||
const server: ServerWithId = {
|
||||
name: 'My server',
|
||||
url: 'https://example.com',
|
||||
apiKey: '123',
|
||||
id: 'abc',
|
||||
};
|
||||
const setUp = (hasAutoConnect = false, autoConnect = false) => render(
|
||||
const setUp = (hasAutoConnect = false, autoConnect = false) => renderWithStore(
|
||||
<MemoryRouter>
|
||||
<Table header={<Table.Row />}>
|
||||
<ManageServersRow server={{ ...server, autoConnect }} hasAutoConnect={hasAutoConnect} />
|
||||
@@ -34,9 +31,9 @@ describe('<ManageServersRow />', () => {
|
||||
expect(screen.getAllByRole('cell')).toHaveLength(expectedCols);
|
||||
});
|
||||
|
||||
it('renders a dropdown', () => {
|
||||
it('renders an options dropdown', () => {
|
||||
setUp();
|
||||
expect(screen.getByText('ManageServersRowDropdown')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Options' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -3,23 +3,22 @@ import type { UserEvent } from '@testing-library/user-event';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import type { ServerWithId } from '../../src/servers/data';
|
||||
import { ManageServersRowDropdownFactory } from '../../src/servers/ManageServersRowDropdown';
|
||||
import { ManageServersRowDropdown } from '../../src/servers/ManageServersRowDropdown';
|
||||
import { checkAccessibility } from '../__helpers__/accessibility';
|
||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||
import { renderWithStore } from '../__helpers__/setUpTest';
|
||||
|
||||
describe('<ManageServersRowDropdown />', () => {
|
||||
const ManageServersRowDropdown = ManageServersRowDropdownFactory(fromPartial({
|
||||
DeleteServerModal: ({ open }: { open: boolean }) => (
|
||||
<span>DeleteServerModal {open ? '[OPEN]' : '[CLOSED]'}</span>
|
||||
),
|
||||
}));
|
||||
const setAutoConnect = vi.fn();
|
||||
const setUp = (autoConnect = false) => {
|
||||
const server = fromPartial<ServerWithId>({ id: 'abc123', autoConnect });
|
||||
return renderWithEvents(
|
||||
return renderWithStore(
|
||||
<MemoryRouter>
|
||||
<ManageServersRowDropdown setAutoConnect={setAutoConnect} server={server} />
|
||||
<ManageServersRowDropdown server={server} />
|
||||
</MemoryRouter>,
|
||||
{
|
||||
initialState: {
|
||||
servers: { [server.id]: server },
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
const toggleDropdown = (user: UserEvent) => user.click(screen.getByRole('button'));
|
||||
@@ -44,26 +43,24 @@ describe('<ManageServersRowDropdown />', () => {
|
||||
expect(screen.getByRole('menuitem', { name: 'Edit server' })).toHaveAttribute('href', '/server/abc123/edit');
|
||||
});
|
||||
|
||||
it('allows toggling auto-connect', async () => {
|
||||
const { user } = setUp();
|
||||
it.each([true, false])('allows toggling auto-connect', async (autoConnect) => {
|
||||
const { user, store } = setUp(autoConnect);
|
||||
|
||||
expect(setAutoConnect).not.toHaveBeenCalled();
|
||||
await toggleDropdown(user);
|
||||
await user.click(screen.getByRole('menuitem', { name: 'Auto-connect' }));
|
||||
expect(setAutoConnect).toHaveBeenCalledWith(expect.objectContaining({ id: 'abc123' }), true);
|
||||
await user.click(screen.getByRole('menuitem', { name: autoConnect ? 'Do not auto-connect' : 'Auto-connect' }));
|
||||
|
||||
expect(Object.values(store.getState().servers)[0].autoConnect).toEqual(!autoConnect);
|
||||
});
|
||||
|
||||
it('renders deletion modal', async () => {
|
||||
const { user } = setUp();
|
||||
|
||||
expect(screen.queryByText('DeleteServerModal [OPEN]')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('DeleteServerModal [CLOSED]')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
|
||||
await toggleDropdown(user);
|
||||
await user.click(screen.getByRole('menuitem', { name: 'Remove server' }));
|
||||
|
||||
expect(screen.getByText('DeleteServerModal [OPEN]')).toBeInTheDocument();
|
||||
expect(screen.queryByText('DeleteServerModal [CLOSED]')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([[true], [false]])('renders expected size and icon', (autoConnect) => {
|
||||
|
||||
@@ -15,11 +15,11 @@ describe('<ServersDropdown />', () => {
|
||||
const setUp = (servers: ServersMap = fallbackServers) => renderWithStore(
|
||||
<MemoryRouter>
|
||||
<ul role="menu">
|
||||
<ServersDropdown servers={servers} />
|
||||
<ServersDropdown />
|
||||
</ul>
|
||||
</MemoryRouter>,
|
||||
{
|
||||
initialState: { selectedServer: null },
|
||||
initialState: { selectedServer: null, servers },
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ exports[`<ManageServersRow /> > renders auto-connect icon only if server is auto
|
||||
class="svg-inline--fa fa-check text-lm-brand dark:text-dm-brand"
|
||||
data-icon="check"
|
||||
data-prefix="fas"
|
||||
data-testid="auto-connect"
|
||||
role="img"
|
||||
viewBox="0 0 448 512"
|
||||
>
|
||||
@@ -56,9 +57,32 @@ exports[`<ManageServersRow /> > renders auto-connect icon only if server is auto
|
||||
<td
|
||||
class="border-lm-border dark:border-dm-border p-2 block lg:table-cell not-last:border-b-1 lg:border-b-1 before:lg:hidden before:content-[attr(data-column)] before:font-bold before:mr-1 text-right max-lg:absolute right-0 -top-1 mx-lg:pt-0"
|
||||
>
|
||||
<span>
|
||||
ManageServersRowDropdown
|
||||
</span>
|
||||
<div
|
||||
class="relative inline-block"
|
||||
>
|
||||
<button
|
||||
aria-controls="_r_o_"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="Options"
|
||||
class="flex items-center rounded-md focus-ring cursor-pointer border border-lm-border dark:border-dm-border bg-lm-primary dark:bg-dm-primary group-[&]/card:bg-lm-input group-[&]/card:dark:bg-dm-input px-3 py-1.5 gap-x-2"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="svg-inline--fa fa-ellipsis-vertical fa-width-auto"
|
||||
data-icon="ellipsis-vertical"
|
||||
data-prefix="fas"
|
||||
role="img"
|
||||
viewBox="0 0 128 512"
|
||||
>
|
||||
<path
|
||||
d="M64 144a56 56 0 1 1 0-112 56 56 0 1 1 0 112zm0 224c30.9 0 56 25.1 56 56s-25.1 56-56 56-56-25.1-56-56 25.1-56 56-56zm56-112c0 30.9-25.1 56-56 56s-56-25.1-56-56 25.1-56 56-56 56 25.1 56 56z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -108,9 +132,32 @@ exports[`<ManageServersRow /> > renders auto-connect icon only if server is auto
|
||||
<td
|
||||
class="border-lm-border dark:border-dm-border p-2 block lg:table-cell not-last:border-b-1 lg:border-b-1 before:lg:hidden before:content-[attr(data-column)] before:font-bold before:mr-1 text-right max-lg:absolute right-0 -top-1 mx-lg:pt-0"
|
||||
>
|
||||
<span>
|
||||
ManageServersRowDropdown
|
||||
</span>
|
||||
<div
|
||||
class="relative inline-block"
|
||||
>
|
||||
<button
|
||||
aria-controls="_r_t_"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="Options"
|
||||
class="flex items-center rounded-md focus-ring cursor-pointer border border-lm-border dark:border-dm-border bg-lm-primary dark:bg-dm-primary group-[&]/card:bg-lm-input group-[&]/card:dark:bg-dm-input px-3 py-1.5 gap-x-2"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="svg-inline--fa fa-ellipsis-vertical fa-width-auto"
|
||||
data-icon="ellipsis-vertical"
|
||||
data-prefix="fas"
|
||||
role="img"
|
||||
viewBox="0 0 128 512"
|
||||
>
|
||||
<path
|
||||
d="M64 144a56 56 0 1 1 0-112 56 56 0 1 1 0 112zm0 224c30.9 0 56 25.1 56 56s-25.1 56-56 56-56-25.1-56-56 25.1-56 56-56zm56-112c0 30.9-25.1 56-56 56s-56-25.1-56-56 25.1-56 56-56 56 25.1 56 56z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -6,7 +6,7 @@ exports[`<ManageServersRowDropdown /> > renders expected size and icon 1`] = `
|
||||
class="relative inline-block"
|
||||
>
|
||||
<button
|
||||
aria-controls="_r_1h_"
|
||||
aria-controls="_r_1v_"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="Options"
|
||||
@@ -28,10 +28,6 @@ exports[`<ManageServersRowDropdown /> > renders expected size and icon 1`] = `
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span>
|
||||
DeleteServerModal
|
||||
[CLOSED]
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -41,7 +37,7 @@ exports[`<ManageServersRowDropdown /> > renders expected size and icon 2`] = `
|
||||
class="relative inline-block"
|
||||
>
|
||||
<button
|
||||
aria-controls="_r_1l_"
|
||||
aria-controls="_r_23_"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="Options"
|
||||
@@ -63,9 +59,5 @@ exports[`<ManageServersRowDropdown /> > renders expected size and icon 2`] = `
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span>
|
||||
DeleteServerModal
|
||||
[CLOSED]
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -6,22 +6,19 @@ import type {
|
||||
import { ImportServersBtnFactory } from '../../../src/servers/helpers/ImportServersBtn';
|
||||
import type { ServersImporter } from '../../../src/servers/services/ServersImporter';
|
||||
import { checkAccessibility } from '../../__helpers__/accessibility';
|
||||
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
||||
import { renderWithStore } from '../../__helpers__/setUpTest';
|
||||
|
||||
describe('<ImportServersBtn />', () => {
|
||||
const csvFile = new File([''], 'servers.csv', { type: 'text/csv' });
|
||||
const onImportMock = vi.fn();
|
||||
const createServersMock = vi.fn();
|
||||
const importServersFromFile = vi.fn().mockResolvedValue([]);
|
||||
const serversImporterMock = fromPartial<ServersImporter>({ importServersFromFile });
|
||||
const ImportServersBtn = ImportServersBtnFactory(fromPartial({ ServersImporter: serversImporterMock }));
|
||||
const setUp = (props: Partial<ImportServersBtnProps> = {}, servers: ServersMap = {}) => renderWithEvents(
|
||||
<ImportServersBtn
|
||||
servers={servers}
|
||||
{...props}
|
||||
createServers={createServersMock}
|
||||
onImport={onImportMock}
|
||||
/>,
|
||||
const setUp = (props: Partial<ImportServersBtnProps> = {}, servers: ServersMap = {}) => renderWithStore(
|
||||
<ImportServersBtn {...props} onImport={onImportMock} />,
|
||||
{
|
||||
initialState: { servers },
|
||||
},
|
||||
);
|
||||
|
||||
it('passes a11y checks', () => checkAccessibility(setUp()));
|
||||
@@ -57,11 +54,8 @@ describe('<ImportServersBtn />', () => {
|
||||
it('imports servers when file input changes', async () => {
|
||||
const { user } = setUp();
|
||||
|
||||
const input = screen.getByTestId('csv-file-input');
|
||||
await user.upload(input, csvFile);
|
||||
|
||||
await user.upload(screen.getByTestId('csv-file-input'), csvFile);
|
||||
expect(importServersFromFile).toHaveBeenCalledTimes(1);
|
||||
expect(createServersMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -78,26 +72,27 @@ describe('<ImportServersBtn />', () => {
|
||||
id: 'existingserver-s.test',
|
||||
};
|
||||
const newServer: ServerData = { name: 'newServer', url: 'http://s.test/newUrl', apiKey: 'newApiKey' };
|
||||
const { user } = setUp({}, { [existingServer.id]: existingServer });
|
||||
const { user, store } = setUp({}, { [existingServer.id]: existingServer });
|
||||
|
||||
importServersFromFile.mockResolvedValue([existingServer, newServer]);
|
||||
importServersFromFile.mockResolvedValue([existingServerData, newServer]);
|
||||
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
await user.upload(screen.getByTestId('csv-file-input'), csvFile);
|
||||
|
||||
// Once the file is uploaded, non-duplicated servers are immediately created
|
||||
expect(createServersMock).toHaveBeenCalledExactlyOnceWith([expect.objectContaining(newServer)]);
|
||||
const { servers } = store.getState();
|
||||
expect(Object.keys(servers)).toHaveLength(2);
|
||||
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('button', { name: btnName }));
|
||||
|
||||
// If duplicated servers are saved, there's one extra call
|
||||
// If duplicated servers are saved, there's one extra server creation
|
||||
if (savesDuplicatedServers) {
|
||||
expect(createServersMock).toHaveBeenLastCalledWith([expect.objectContaining(existingServerData)]);
|
||||
const { servers } = store.getState();
|
||||
expect(Object.keys(servers)).toHaveLength(3);
|
||||
}
|
||||
|
||||
// On import is called only once, no matter what
|
||||
expect(onImportMock).toHaveBeenCalledOnce();
|
||||
expect(createServersMock).toHaveBeenCalledTimes(savesDuplicatedServers ? 2 : 1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,18 +2,17 @@ import { screen } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import type { NonReachableServer, NotFoundServer, SelectedServer } from '../../../src/servers/data';
|
||||
import { ServerErrorFactory } from '../../../src/servers/helpers/ServerError';
|
||||
import { ServerError } from '../../../src/servers/helpers/ServerError';
|
||||
import { checkAccessibility } from '../../__helpers__/accessibility';
|
||||
import { renderWithStore } from '../../__helpers__/setUpTest';
|
||||
|
||||
describe('<ServerError />', () => {
|
||||
const ServerError = ServerErrorFactory(fromPartial({ DeleteServerButton: () => null }));
|
||||
const setUp = (selectedServer: SelectedServer) => renderWithStore(
|
||||
<MemoryRouter>
|
||||
<ServerError servers={{}} />
|
||||
<ServerError />
|
||||
</MemoryRouter>,
|
||||
{
|
||||
initialState: { selectedServer },
|
||||
initialState: { selectedServer, servers: {} },
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user