Refactor DI approach for components

This commit is contained in:
Alejandro Celaya
2023-09-05 09:08:42 +02:00
parent 046f79270a
commit 6926afbac1
30 changed files with 371 additions and 234 deletions

View File

@@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { ErrorHandler as createErrorHandler } from '../../src/common/ErrorHandler';
import type { PropsWithChildren } from 'react';
import { ErrorHandler as BaseErrorHandler } from '../../src/common/ErrorHandler';
import { renderWithEvents } from '../__helpers__/setUpTest';
const ComponentWithError = () => {
@@ -9,18 +10,16 @@ const ComponentWithError = () => {
describe('<ErrorHandler />', () => {
const reload = vi.fn();
const window = fromPartial<Window>({
location: { reload },
});
const location = fromPartial<Window['location']>({ reload });
const cons = fromPartial<Console>({ error: vi.fn() });
const ErrorHandler = createErrorHandler(window, cons);
const ErrorHandler = (props: PropsWithChildren) => <BaseErrorHandler console={cons} location={location} {...props} />;
beforeEach(() => {
vi.spyOn(console, 'error').mockImplementation(() => {}); // Silence react errors
});
it('renders children when no error has occurred', () => {
render(<ErrorHandler children={<span>Foo</span>} />);
render(<ErrorHandler><span>Foo</span></ErrorHandler>);
expect(screen.getByText('Foo')).toBeInTheDocument();
expect(screen.queryByText('Oops! This is awkward :S')).not.toBeInTheDocument();
@@ -28,14 +27,14 @@ describe('<ErrorHandler />', () => {
});
it('renders error page when error has occurred', () => {
render(<ErrorHandler children={<ComponentWithError />} />);
render(<ErrorHandler><ComponentWithError /></ErrorHandler>);
expect(screen.getByText('Oops! This is awkward :S')).toBeInTheDocument();
expect(screen.getByRole('button')).toBeInTheDocument();
});
it('reloads page on button click', async () => {
const { user } = renderWithEvents(<ErrorHandler children={<ComponentWithError />} />);
const { user } = renderWithEvents(<ErrorHandler><ComponentWithError /></ErrorHandler>);
expect(reload).not.toHaveBeenCalled();
await user.click(screen.getByRole('button'));

View File

@@ -1,11 +1,14 @@
import { screen, waitFor } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import { MainHeader as createMainHeader } from '../../src/common/MainHeader';
import { MainHeaderFactory } from '../../src/common/MainHeader';
import { renderWithEvents } from '../__helpers__/setUpTest';
describe('<MainHeader />', () => {
const MainHeader = createMainHeader(() => <>ServersDropdown</>);
const MainHeader = MainHeaderFactory(fromPartial({
ServersDropdown: () => <>ServersDropdown</>,
}));
const setUp = (pathname = '') => {
const history = createMemoryHistory();
history.push(pathname);

View File

@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { useParams } from 'react-router-dom';
import { ShlinkWebComponentContainer as createContainer } from '../../src/common/ShlinkWebComponentContainer';
import { ShlinkWebComponentContainerFactory } from '../../src/common/ShlinkWebComponentContainer';
import type { NonReachableServer, NotFoundServer, SelectedServer } from '../../src/servers/data';
vi.mock('react-router-dom', async () => ({
@@ -10,12 +10,12 @@ vi.mock('react-router-dom', async () => ({
}));
describe('<ShlinkWebComponentContainer />', () => {
const ShlinkWebComponentContainer = createContainer(
vi.fn().mockReturnValue(fromPartial({})),
fromPartial({}),
() => <>ShlinkWebComponent</>,
() => <>ServerError</>,
);
const ShlinkWebComponentContainer = ShlinkWebComponentContainerFactory(fromPartial({
buildShlinkApiClient: vi.fn().mockReturnValue(fromPartial({})),
TagColorsStorage: fromPartial({}),
ShlinkWebComponent: () => <>ShlinkWebComponent</>,
ServerError: () => <>ServerError</>,
}));
const setUp = (selectedServer: SelectedServer) => render(
<ShlinkWebComponentContainer selectServer={vi.fn()} selectedServer={selectedServer} settings={{}} />,
);

View File

@@ -1,7 +1,7 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { useNavigate } from 'react-router-dom';
import { CreateServer as createCreateServer } from '../../src/servers/CreateServer';
import { CreateServerFactory } from '../../src/servers/CreateServer';
import type { ServersMap } from '../../src/servers/data';
import { renderWithEvents } from '../__helpers__/setUpTest';
@@ -31,7 +31,10 @@ describe('<CreateServer />', () => {
callCount += 1;
return result;
});
const CreateServer = createCreateServer(() => <>ImportServersBtn</>, useTimeoutToggle);
const CreateServer = CreateServerFactory(fromPartial({
ImportServersBtn: () => <>ImportServersBtn</>,
useTimeoutToggle,
}));
return renderWithEvents(<CreateServer createServers={createServersMock} servers={servers} />);
};

View File

@@ -1,13 +1,14 @@
import { screen, waitFor } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import type { ReactNode } from 'react';
import { DeleteServerButton as createDeleteServerButton } from '../../src/servers/DeleteServerButton';
import { DeleteServerButtonFactory } from '../../src/servers/DeleteServerButton';
import type { DeleteServerModalProps } from '../../src/servers/DeleteServerModal';
import { renderWithEvents } from '../__helpers__/setUpTest';
describe('<DeleteServerButton />', () => {
const DeleteServerButton = createDeleteServerButton(
({ isOpen }) => <>DeleteServerModal {isOpen ? '[Open]' : '[Closed]'}</>,
);
const DeleteServerButton = DeleteServerButtonFactory(fromPartial({
DeleteServerModal: ({ isOpen }: DeleteServerModalProps) => <>DeleteServerModal {isOpen ? '[Open]' : '[Closed]'}</>,
}));
const setUp = (children?: ReactNode) => renderWithEvents(
<DeleteServerButton server={fromPartial({})} textClassName="button">{children}</DeleteServerButton>,
);

View File

@@ -2,7 +2,7 @@ import { fireEvent, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter, useNavigate } from 'react-router-dom';
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
import { EditServer as editServerConstruct } from '../../src/servers/EditServer';
import { EditServerFactory } from '../../src/servers/EditServer';
import { renderWithEvents } from '../__helpers__/setUpTest';
vi.mock('react-router-dom', async () => ({
@@ -20,7 +20,7 @@ describe('<EditServer />', () => {
url: 'the_url',
apiKey: 'the_api_key',
});
const EditServer = editServerConstruct(ServerError);
const EditServer = EditServerFactory(fromPartial({ ServerError }));
const setUp = (selectedServer: SelectedServer = defaultSelectedServer) => renderWithEvents(
<MemoryRouter>
<EditServer editServer={editServerMock} selectedServer={selectedServer} selectServer={vi.fn()} />

View File

@@ -2,7 +2,7 @@ import { screen, waitFor } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { ServersMap, ServerWithId } from '../../src/servers/data';
import { ManageServers as createManageServers } from '../../src/servers/ManageServers';
import { ManageServersFactory } from '../../src/servers/ManageServers';
import type { ServersExporter } from '../../src/servers/services/ServersExporter';
import { renderWithEvents } from '../__helpers__/setUpTest';
@@ -10,12 +10,14 @@ describe('<ManageServers />', () => {
const exportServers = vi.fn();
const serversExporter = fromPartial<ServersExporter>({ exportServers });
const useTimeoutToggle = vi.fn().mockReturnValue([false, vi.fn()]);
const ManageServers = createManageServers(
serversExporter,
() => <span>ImportServersBtn</span>,
const ManageServers = ManageServersFactory(fromPartial({
ServersExporter: serversExporter,
ImportServersBtn: () => <span>ImportServersBtn</span>,
useTimeoutToggle,
({ hasAutoConnect }) => <tr><td>ManageServersRow {hasAutoConnect ? '[YES]' : '[NO]'}</td></tr>,
);
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 },
);

View File

@@ -1,10 +1,13 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { ServerWithId } from '../../src/servers/data';
import { ManageServersRow as createManageServersRow } from '../../src/servers/ManageServersRow';
import { ManageServersRowFactory } from '../../src/servers/ManageServersRow';
describe('<ManageServersRow />', () => {
const ManageServersRow = createManageServersRow(() => <span>ManageServersRowDropdown</span>);
const ManageServersRow = ManageServersRowFactory(fromPartial({
ManageServersRowDropdown: () => <span>ManageServersRowDropdown</span>,
}));
const server: ServerWithId = {
name: 'My server',
url: 'https://example.com',

View File

@@ -2,13 +2,15 @@ import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { ServerWithId } from '../../src/servers/data';
import { ManageServersRowDropdown as createManageServersRowDropdown } from '../../src/servers/ManageServersRowDropdown';
import { ManageServersRowDropdownFactory } from '../../src/servers/ManageServersRowDropdown';
import { renderWithEvents } from '../__helpers__/setUpTest';
describe('<ManageServersRowDropdown />', () => {
const ManageServersRowDropdown = createManageServersRowDropdown(
({ isOpen }) => <span>DeleteServerModal {isOpen ? '[OPEN]' : '[CLOSED]'}</span>,
);
const ManageServersRowDropdown = ManageServersRowDropdownFactory(fromPartial({
DeleteServerModal: ({ isOpen }: { isOpen: boolean }) => (
<span>DeleteServerModal {isOpen ? '[OPEN]' : '[CLOSED]'}</span>
),
}));
const setAutoConnect = vi.fn();
const setUp = (autoConnect = false) => {
const server = fromPartial<ServerWithId>({ id: 'abc123', autoConnect });

View File

@@ -3,9 +3,7 @@ import { fromPartial } from '@total-typescript/shoehorn';
import type { ServersMap, ServerWithId } from '../../../src/servers/data';
import type {
ImportServersBtnProps } from '../../../src/servers/helpers/ImportServersBtn';
import {
ImportServersBtn as createImportServersBtn,
} from '../../../src/servers/helpers/ImportServersBtn';
import { ImportServersBtnFactory } from '../../../src/servers/helpers/ImportServersBtn';
import type { ServersImporter } from '../../../src/servers/services/ServersImporter';
import { renderWithEvents } from '../../__helpers__/setUpTest';
@@ -14,7 +12,7 @@ describe('<ImportServersBtn />', () => {
const createServersMock = vi.fn();
const importServersFromFile = vi.fn().mockResolvedValue([]);
const serversImporterMock = fromPartial<ServersImporter>({ importServersFromFile });
const ImportServersBtn = createImportServersBtn(serversImporterMock);
const ImportServersBtn = ImportServersBtnFactory(fromPartial({ ServersImporter: serversImporterMock }));
const setUp = (props: Partial<ImportServersBtnProps> = {}, servers: ServersMap = {}) => renderWithEvents(
<ImportServersBtn
servers={servers}

View File

@@ -2,10 +2,10 @@ import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { NonReachableServer, NotFoundServer } from '../../../src/servers/data';
import { ServerError as createServerError } from '../../../src/servers/helpers/ServerError';
import { ServerErrorFactory } from '../../../src/servers/helpers/ServerError';
describe('<ServerError />', () => {
const ServerError = createServerError(() => null);
const ServerError = ServerErrorFactory(fromPartial({ DeleteServerButton: () => null }));
it.each([
[

View File

@@ -1,17 +1,18 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import { Settings as createSettings } from '../../src/settings/Settings';
import { SettingsFactory } from '../../src/settings/Settings';
describe('<Settings />', () => {
const Settings = createSettings(
() => <span>RealTimeUpdates</span>,
() => <span>ShortUrlCreation</span>,
() => <span>ShortUrlsList</span>,
() => <span>UserInterface</span>,
() => <span>Visits</span>,
() => <span>Tags</span>,
);
const Settings = SettingsFactory(fromPartial({
RealTimeUpdatesSettings: () => <span>RealTimeUpdates</span>,
ShortUrlCreationSettings: () => <span>ShortUrlCreation</span>,
ShortUrlsListSettings: () => <span>ShortUrlsList</span>,
UserInterfaceSettings: () => <span>UserInterface</span>,
VisitsSettings: () => <span>Visits</span>,
TagsSettings: () => <span>Tags</span>,
}));
const setUp = (activeRoute = '/') => {
const history = createMemoryHistory();
history.push(activeRoute);