From d10bea50bc56942c78d149f00111e360ce7a20b9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 15 Nov 2025 11:46:19 +0100 Subject: [PATCH] Do not inject components into other components --- src/app/App.tsx | 28 ++------- src/common/Home.tsx | 5 +- src/common/ShlinkWebComponentContainer.tsx | 22 ++++--- src/container/context.ts | 24 -------- src/container/context.tsx | 60 +++++++++++++++++++ src/container/index.ts | 24 -------- src/container/utils.ts | 27 --------- src/index.tsx | 2 +- src/servers/CreateServer.tsx | 22 +++---- src/servers/ManageServers.tsx | 29 ++++----- src/servers/helpers/ImportServersBtn.tsx | 20 +++---- test/app/App.test.tsx | 24 ++++---- test/common/Home.test.tsx | 5 +- .../ShlinkWebComponentContainer.test.tsx | 11 ++-- test/servers/CreateServer.test.tsx | 20 +++---- test/servers/ManageServers.test.tsx | 19 +++--- .../servers/helpers/ImportServersBtn.test.tsx | 11 ++-- 17 files changed, 156 insertions(+), 197 deletions(-) delete mode 100644 src/container/context.ts create mode 100644 src/container/context.tsx delete mode 100644 src/container/utils.ts diff --git a/src/app/App.tsx b/src/app/App.tsx index 078fef99..c295ba6c 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -4,33 +4,22 @@ import type { FC } from 'react'; import { useEffect } from 'react'; import { Route, Routes, useLocation } from 'react-router'; import { AppUpdateBanner } from '../common/AppUpdateBanner'; +import { Home } from '../common/Home'; import { MainHeader } from '../common/MainHeader'; import { NotFound } from '../common/NotFound'; import { ShlinkVersionsContainer } from '../common/ShlinkVersionsContainer'; -import type { FCWithDeps } from '../container/utils'; -import { componentFactory, useDependencies } from '../container/utils'; +import { ShlinkWebComponentContainer } from '../common/ShlinkWebComponentContainer'; +import { CreateServer } from '../servers/CreateServer'; import { EditServer } from '../servers/EditServer'; +import { ManageServers } from '../servers/ManageServers'; import { useLoadRemoteServers } from '../servers/reducers/remoteServers'; import { useSettings } from '../settings/reducers/settings'; import { Settings } from '../settings/Settings'; import { forceUpdate } from '../utils/helpers/sw'; import { useAppUpdated } from './reducers/appUpdates'; -type AppDeps = { - Home: FC; - ShlinkWebComponentContainer: FC; - CreateServer: FC; - ManageServers: FC; -}; - -const App: FCWithDeps = () => { +export const App: FC = () => { const { appUpdated, resetAppUpdate } = useAppUpdated(); - const { - Home, - ShlinkWebComponentContainer, - CreateServer, - ManageServers, - } = useDependencies(App); useLoadRemoteServers(); @@ -80,10 +69,3 @@ const App: FCWithDeps = () => { ); }; - -export const AppFactory = componentFactory(App, [ - 'Home', - 'ShlinkWebComponentContainer', - 'CreateServer', - 'ManageServers', -]); diff --git a/src/common/Home.tsx b/src/common/Home.tsx index 94719b09..ba628837 100644 --- a/src/common/Home.tsx +++ b/src/common/Home.tsx @@ -6,11 +6,12 @@ import type { FC } from 'react'; import { useEffect } from 'react'; import { ExternalLink } from 'react-external-link'; import { useNavigate } from 'react-router'; +import { withoutSelectedServer } from '../servers/helpers/withoutSelectedServer'; import { useServers } from '../servers/reducers/servers'; import { ServersListGroup } from '../servers/ServersListGroup'; import { ShlinkLogo } from './img/ShlinkLogo'; -export const Home: FC = () => { +export const Home: FC = withoutSelectedServer(() => { const navigate = useNavigate(); const { servers } = useServers(); const serversList = Object.values(servers); @@ -66,4 +67,4 @@ export const Home: FC = () => { ); -}; +}); diff --git a/src/common/ShlinkWebComponentContainer.tsx b/src/common/ShlinkWebComponentContainer.tsx index c343448f..d07aba3d 100644 --- a/src/common/ShlinkWebComponentContainer.tsx +++ b/src/common/ShlinkWebComponentContainer.tsx @@ -4,10 +4,10 @@ import { ShlinkSidebarVisibilityProvider, ShlinkWebComponent, } from '@shlinkio/shlink-web-component'; +import type { FC } from 'react'; import { memo } from 'react'; import type { ShlinkApiClientBuilder } from '../api/services/ShlinkApiClientBuilder'; -import type { FCWithDeps } from '../container/utils'; -import { componentFactory, useDependencies } from '../container/utils'; +import { withDependencies } from '../container/context'; import { isReachableServer } from '../servers/data'; import { ServerError } from '../servers/helpers/ServerError'; import { withSelectedServer } from '../servers/helpers/withSelectedServer'; @@ -15,23 +15,21 @@ import { useSelectedServer } from '../servers/reducers/selectedServer'; import { useSettings } from '../settings/reducers/settings'; import { NotFound } from './NotFound'; -type ShlinkWebComponentContainerDeps = { +export type ShlinkWebComponentContainerProps = { TagColorsStorage: TagColorsStorage; buildShlinkApiClient: ShlinkApiClientBuilder; }; -const ShlinkWebComponentContainer: FCWithDeps< - any, - ShlinkWebComponentContainerDeps +const ShlinkWebComponentContainerBase: FC< + ShlinkWebComponentContainerProps // FIXME Using `memo` here to solve a flickering effect in charts. // memo is probably not the right solution. The root cause is the withSelectedServer HOC, but I couldn't fix the // extra rendering there. // This should be revisited at some point. -> = withSelectedServer(memo(() => { - const { - buildShlinkApiClient, - TagColorsStorage: tagColorsStorage, - } = useDependencies(ShlinkWebComponentContainer); +> = withSelectedServer(memo(({ + buildShlinkApiClient, + TagColorsStorage: tagColorsStorage, +}) => { const { selectedServer } = useSelectedServer(); const { settings } = useSettings(); @@ -58,7 +56,7 @@ const ShlinkWebComponentContainer: FCWithDeps< ); })); -export const ShlinkWebComponentContainerFactory = componentFactory(ShlinkWebComponentContainer, [ +export const ShlinkWebComponentContainer = withDependencies(ShlinkWebComponentContainerBase, [ 'buildShlinkApiClient', 'TagColorsStorage', ]); diff --git a/src/container/context.ts b/src/container/context.ts deleted file mode 100644 index f5a47d91..00000000 --- a/src/container/context.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { IContainer } from 'bottlejs'; -import { createContext, useContext } from 'react'; - -const ContainerContext = createContext(null); - -export const ContainerProvider = ContainerContext.Provider; - -export const useDependencies = (...names: string[]): T => { - const container = useContext(ContainerContext); - if (!container) { - throw new Error('You cannot use "useDependencies" outside of a ContainerProvider'); - } - - return names.map((name) => { - const dependency = container[name]; - if (!dependency) { - throw new Error(`Dependency with name "${name}" not found in container`); - } - - return dependency; - }) as T; -}; - -// TODO Create Higher Order Component that can pull dependencies from the container diff --git a/src/container/context.tsx b/src/container/context.tsx new file mode 100644 index 00000000..a1131943 --- /dev/null +++ b/src/container/context.tsx @@ -0,0 +1,60 @@ +import type { IContainer } from 'bottlejs'; +import { type ComponentType, createContext, useContext } from 'react'; + +const ContainerContext = createContext(null); + +export const ContainerProvider = ContainerContext.Provider; + +const useContainer = (wrapperName: string): IContainer => { + const container = useContext(ContainerContext); + if (!container) { + throw new Error(`You cannot use "${wrapperName}" outside of a ContainerProvider`); + } + + return container; +}; + +/** + * Hook used to extract dependencies from the container in other hooks. + */ +export const useDependencies = (...names: string[]): T => { + const container = useContainer('useDependencies'); + + return names.map((name) => { + const dependency = container[name]; + if (!dependency) { + throw new Error(`Dependency with name "${name}" not found in container`); + } + + return dependency; + }) as T; +}; + +/** + * Higher Order Component used to inject services into components as props. + */ +export function withDependencies< + Props extends Record, + DependencyName extends string & keyof Props, +>( + Component: ComponentType, + dependencyNames: DependencyName[], +): ComponentType> { + function Wrapper(props: Omit) { + const container = useContainer('withDependencies'); + + // Inject services, unless they have been overridden by props passed from + // the parent component. + const dependencies: Partial> = {}; + for (const dependency of dependencyNames) { + if (!(dependency in props)) { + dependencies[dependency] = container[dependency]; + } + } + + const propsWithServices = { ...dependencies, ...props } as Props; + return ; + } + + return Wrapper; +} diff --git a/src/container/index.ts b/src/container/index.ts index 25102bcf..c69ab776 100644 --- a/src/container/index.ts +++ b/src/container/index.ts @@ -2,13 +2,6 @@ import { useTimeoutToggle } from '@shlinkio/shlink-frontend-kit'; import { FetchHttpClient } from '@shlinkio/shlink-js-sdk/fetch'; import Bottle from 'bottlejs'; import { buildShlinkApiClient } from '../api/services/ShlinkApiClientBuilder'; -import { AppFactory } from '../app/App'; -import { Home } from '../common/Home'; -import { ShlinkWebComponentContainerFactory } from '../common/ShlinkWebComponentContainer'; -import { CreateServerFactory } from '../servers/CreateServer'; -import { ImportServersBtnFactory } from '../servers/helpers/ImportServersBtn'; -import { withoutSelectedServer } from '../servers/helpers/withoutSelectedServer'; -import { ManageServersFactory } from '../servers/ManageServers'; import { ServersExporter } from '../servers/services/ServersExporter'; import { ServersImporter } from '../servers/services/ServersImporter'; import { csvToJson, jsonToCsv } from '../utils/helpers/csvjson'; @@ -35,22 +28,5 @@ bottle.serviceFactory('useTimeoutToggle', () => useTimeoutToggle); bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'HttpClient'); -// Components -bottle.factory('App', AppFactory); - -bottle.serviceFactory('Home', () => Home); -bottle.decorator('Home', withoutSelectedServer); - -bottle.factory('ShlinkWebComponentContainer', ShlinkWebComponentContainerFactory); - -bottle.factory('ManageServers', ManageServersFactory); -bottle.decorator('ManageServers', withoutSelectedServer); - -bottle.factory('CreateServer', CreateServerFactory); -bottle.decorator('CreateServer', withoutSelectedServer); - -bottle.factory('ImportServersBtn', ImportServersBtnFactory); - -// Services bottle.service('ServersImporter', ServersImporter, 'csvToJson'); bottle.service('ServersExporter', ServersExporter, 'Storage', 'window', 'jsonToCsv'); diff --git a/src/container/utils.ts b/src/container/utils.ts deleted file mode 100644 index 0da77ccb..00000000 --- a/src/container/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { IContainer } from 'bottlejs'; -import type { FC } from 'react'; -import { useMemo } from 'react'; - -export type FCWithDeps = FC & Partial; - -export function useDependencies(obj: Deps): Omit, keyof FC> { - return useMemo(() => obj as Omit, keyof FC>, [obj]); -} - -export function componentFactory, keyof FC>>( - Component: CompType, - deps: ReadonlyArray, -) { - return (container: IContainer, console = globalThis.console) => { - deps.forEach((dep) => { - const resolvedDependency = container[dep as string]; - if (!resolvedDependency && process.env.NODE_ENV !== 'production') { - console.error(`[Debug] Could not find "${dep as string}" dependency in container`); - } - - Component[dep] = resolvedDependency; - }); - - return Component; - }; -} diff --git a/src/index.tsx b/src/index.tsx index f7022a69..e3c85359 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router'; import pack from '../package.json'; +import { App } from './app/App'; import { appUpdateAvailable } from './app/reducers/appUpdates'; import { ErrorHandler } from './common/ErrorHandler'; import { ScrollToTop } from './common/ScrollToTop'; @@ -12,7 +13,6 @@ import { setUpStore } from './store'; import './tailwind.css'; const store = setUpStore(); -const { App } = container; createRoot(document.getElementById('root')!).render( diff --git a/src/servers/CreateServer.tsx b/src/servers/CreateServer.tsx index 402b0f62..332160ce 100644 --- a/src/servers/CreateServer.tsx +++ b/src/servers/CreateServer.tsx @@ -1,23 +1,22 @@ -import type { ResultProps,TimeoutToggle } from '@shlinkio/shlink-frontend-kit'; -import { Button, Result,useToggle } from '@shlinkio/shlink-frontend-kit'; +import type { ResultProps, TimeoutToggle } from '@shlinkio/shlink-frontend-kit'; +import { Button, Result, useToggle } from '@shlinkio/shlink-frontend-kit'; import type { FC } from 'react'; import { useCallback, useState } from 'react'; import { useNavigate } from 'react-router'; import { NoMenuLayout } from '../common/NoMenuLayout'; -import type { FCWithDeps } from '../container/utils'; -import { componentFactory, useDependencies } from '../container/utils'; +import { withDependencies } from '../container/context'; import { useGoBack } from '../utils/helpers/hooks'; import type { ServerData } from './data'; import { ensureUniqueIds } from './helpers'; import { DuplicatedServersModal } from './helpers/DuplicatedServersModal'; -import type { ImportServersBtnProps } from './helpers/ImportServersBtn'; +import { ImportServersBtn } from './helpers/ImportServersBtn'; import { ServerForm } from './helpers/ServerForm'; +import { withoutSelectedServer } from './helpers/withoutSelectedServer'; import { useServers } from './reducers/servers'; const SHOW_IMPORT_MSG_TIME = 4000; -type CreateServerDeps = { - ImportServersBtn: FC; +export type CreateServerProps = { useTimeoutToggle: TimeoutToggle; }; @@ -30,15 +29,12 @@ const ImportResult = ({ variant }: Pick) => ( ); -const CreateServer: FCWithDeps = () => { +const CreateServerBase: FC = withoutSelectedServer(({ useTimeoutToggle }) => { const { servers, createServers } = useServers(); - const { ImportServersBtn, useTimeoutToggle } = useDependencies(CreateServer); const navigate = useNavigate(); const goBack = useGoBack(); const hasServers = !!Object.keys(servers).length; - // eslint-disable-next-line react-compiler/react-compiler const [serversImported, setServersImported] = useTimeoutToggle({ delay: SHOW_IMPORT_MSG_TIME }); - // eslint-disable-next-line react-compiler/react-compiler const [errorImporting, setErrorImporting] = useTimeoutToggle({ delay: SHOW_IMPORT_MSG_TIME }); const { flag: isConfirmModalOpen, toggle: toggleConfirmModal } = useToggle(); const [serverData, setServerData] = useState(); @@ -83,6 +79,6 @@ const CreateServer: FCWithDeps = () => { /> ); -}; +}); -export const CreateServerFactory = componentFactory(CreateServer, ['ImportServersBtn', 'useTimeoutToggle']); +export const CreateServer = withDependencies(CreateServerBase, ['useTimeoutToggle']); diff --git a/src/servers/ManageServers.tsx b/src/servers/ManageServers.tsx index f07ae658..130f405b 100644 --- a/src/servers/ManageServers.tsx +++ b/src/servers/ManageServers.tsx @@ -5,27 +5,24 @@ import { Button, Result, SearchInput, SimpleCard, Table } from '@shlinkio/shlink import type { FC } from 'react'; import { useMemo, useState } from 'react'; import { NoMenuLayout } from '../common/NoMenuLayout'; -import type { FCWithDeps } from '../container/utils'; -import { componentFactory, useDependencies } from '../container/utils'; -import type { ImportServersBtnProps } from './helpers/ImportServersBtn'; +import { withDependencies } from '../container/context'; +import { ImportServersBtn } from './helpers/ImportServersBtn'; +import { withoutSelectedServer } from './helpers/withoutSelectedServer'; import { ManageServersRow } from './ManageServersRow'; import { useServers } from './reducers/servers'; import type { ServersExporter } from './services/ServersExporter'; -type ManageServersDeps = { +export type ManageServersProps = { ServersExporter: ServersExporter; - ImportServersBtn: FC; useTimeoutToggle: TimeoutToggle; }; const SHOW_IMPORT_MSG_TIME = 4000; -const ManageServers: FCWithDeps = () => { - const { - ServersExporter: serversExporter, - ImportServersBtn, - useTimeoutToggle, - } = useDependencies(ManageServers); +const ManageServersBase: FC = withoutSelectedServer(({ + ServersExporter: serversExporter, + useTimeoutToggle, +}) => { const { servers } = useServers(); const [searchTerm, setSearchTerm] = useState(''); const allServers = useMemo(() => Object.values(servers), [servers]); @@ -34,7 +31,7 @@ const ManageServers: FCWithDeps = () => { [allServers, searchTerm], ); const hasAutoConnect = allServers.some(({ autoConnect }) => !!autoConnect); - // eslint-disable-next-line react-compiler/react-compiler + const [errorImporting, setErrorImporting] = useTimeoutToggle({ delay: SHOW_IMPORT_MSG_TIME }); return ( @@ -82,10 +79,6 @@ const ManageServers: FCWithDeps = () => { )} ); -}; +}); -export const ManageServersFactory = componentFactory(ManageServers, [ - 'ServersExporter', - 'ImportServersBtn', - 'useTimeoutToggle', -]); +export const ManageServers = withDependencies(ManageServersBase, ['ServersExporter', 'useTimeoutToggle']); diff --git a/src/servers/helpers/ImportServersBtn.tsx b/src/servers/helpers/ImportServersBtn.tsx index 37c4b236..3b774e5a 100644 --- a/src/servers/helpers/ImportServersBtn.tsx +++ b/src/servers/helpers/ImportServersBtn.tsx @@ -1,10 +1,9 @@ import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, Tooltip, useToggle , useTooltip } from '@shlinkio/shlink-frontend-kit'; -import type { ChangeEvent, PropsWithChildren } from 'react'; +import { Button, Tooltip, useToggle, useTooltip } from '@shlinkio/shlink-frontend-kit'; +import type { ChangeEvent, FC, PropsWithChildren } from 'react'; import { useCallback, useRef, useState } from 'react'; -import type { FCWithDeps } from '../../container/utils'; -import { componentFactory, useDependencies } from '../../container/utils'; +import { withDependencies } from '../../container/context'; import type { ServerData } from '../data'; import { useServers } from '../reducers/servers'; import type { ServersImporter } from '../services/ServersImporter'; @@ -16,21 +15,20 @@ export type ImportServersBtnProps = PropsWithChildren<{ onError?: (error: Error) => void; tooltipPlacement?: 'top' | 'bottom'; className?: string; + + // Injected + ServersImporter: ServersImporter }>; -type ImportServersBtnDeps = { - ServersImporter: ServersImporter -}; - -const ImportServersBtn: FCWithDeps = ({ +const ImportServersBtnBase: FC = ({ children, onImport, onError = () => {}, tooltipPlacement = 'bottom', className = '', + ServersImporter: serversImporter, }) => { const { createServers, servers } = useServers(); - const { ServersImporter: serversImporter } = useDependencies(ImportServersBtn); const fileInputRef = useRef(null); const { anchor, tooltip } = useTooltip({ placement: tooltipPlacement }); const [duplicatedServers, setDuplicatedServers] = useState([]); @@ -106,4 +104,4 @@ const ImportServersBtn: FCWithDeps ); }; -export const ImportServersBtnFactory = componentFactory(ImportServersBtn, ['ServersImporter']); +export const ImportServersBtn = withDependencies(ImportServersBtnBase, ['ServersImporter']); diff --git a/test/app/App.test.tsx b/test/app/App.test.tsx index 694d12a9..691f7de7 100644 --- a/test/app/App.test.tsx +++ b/test/app/App.test.tsx @@ -2,25 +2,25 @@ import type { HttpClient } from '@shlinkio/shlink-js-sdk'; 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 { App } from '../../src/app/App'; import { ContainerProvider } from '../../src/container/context'; import type { ServerWithId } from '../../src/servers/data'; import { checkAccessibility } from '../__helpers__/accessibility'; import { renderWithStore } from '../__helpers__/setUpTest'; +vi.mock(import('../../src/common/ShlinkWebComponentContainer'), () => ({ + ShlinkWebComponentContainer: () => ShlinkWebComponentContainer, +})); + describe('', () => { - const App = AppFactory( - fromPartial({ - Home: () => <>Home, - ShlinkWebComponentContainer: () => <>ShlinkWebComponentContainer, - CreateServer: () => <>CreateServer, - ManageServers: () => <>ManageServers, - }), - ); const setUp = async (activeRoute = '/') => act(() => renderWithStore( ({}), buildShlinkApiClient: vi.fn() })} + value={fromPartial({ + HttpClient: fromPartial({}), + buildShlinkApiClient: vi.fn(), + useTimeoutToggle: vi.fn().mockReturnValue([false, vi.fn()]), + })} > @@ -42,8 +42,8 @@ describe('', () => { it.each([ ['/settings/general', 'User interface'], ['/settings/short-urls', 'Short URLs form'], - ['/manage-servers', 'ManageServers'], - ['/server/create', 'CreateServer'], + ['/manage-servers', 'Add a server'], + ['/server/create', 'Add new server'], ['/server/abc123/edit', 'Edit "abc123 server"'], ['/server/def456/edit', 'Edit "def456 server"'], ['/server/abc123/foo', 'ShlinkWebComponentContainer'], diff --git a/test/common/Home.test.tsx b/test/common/Home.test.tsx index 84b36a5f..e9521d20 100644 --- a/test/common/Home.test.tsx +++ b/test/common/Home.test.tsx @@ -2,6 +2,7 @@ import { screen } from '@testing-library/react'; import { fromPartial } from '@total-typescript/shoehorn'; import { MemoryRouter } from 'react-router'; import { Home } from '../../src/common/Home'; +import { ContainerProvider } from '../../src/container/context'; import type { ServersMap, ServerWithId } from '../../src/servers/data'; import { checkAccessibility } from '../__helpers__/accessibility'; import { renderWithStore } from '../__helpers__/setUpTest'; @@ -9,7 +10,9 @@ import { renderWithStore } from '../__helpers__/setUpTest'; describe('', () => { const setUp = (servers: ServersMap = {}) => renderWithStore( - + + + , { initialState: { servers }, diff --git a/test/common/ShlinkWebComponentContainer.test.tsx b/test/common/ShlinkWebComponentContainer.test.tsx index 0efca169..fb31f69d 100644 --- a/test/common/ShlinkWebComponentContainer.test.tsx +++ b/test/common/ShlinkWebComponentContainer.test.tsx @@ -1,7 +1,7 @@ import { screen } from '@testing-library/react'; import { fromPartial } from '@total-typescript/shoehorn'; import { MemoryRouter } from 'react-router'; -import { ShlinkWebComponentContainerFactory } from '../../src/common/ShlinkWebComponentContainer'; +import { ShlinkWebComponentContainer } from '../../src/common/ShlinkWebComponentContainer'; import { ContainerProvider } from '../../src/container/context'; import type { NonReachableServer, NotFoundServer, SelectedServer } from '../../src/servers/data'; import { checkAccessibility } from '../__helpers__/accessibility'; @@ -14,13 +14,12 @@ vi.mock('@shlinkio/shlink-web-component', () => ({ })); describe('', () => { - const ShlinkWebComponentContainer = ShlinkWebComponentContainerFactory(fromPartial({ - buildShlinkApiClient: vi.fn().mockReturnValue(fromPartial({})), - TagColorsStorage: fromPartial({}), - })); const setUp = (selectedServer: SelectedServer) => renderWithStore( - + , diff --git a/test/servers/CreateServer.test.tsx b/test/servers/CreateServer.test.tsx index 05375b48..2905b783 100644 --- a/test/servers/CreateServer.test.tsx +++ b/test/servers/CreateServer.test.tsx @@ -2,7 +2,8 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'; import { fromPartial } from '@total-typescript/shoehorn'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router'; -import { CreateServerFactory } from '../../src/servers/CreateServer'; +import { ContainerProvider } from '../../src/container/context'; +import { CreateServer } from '../../src/servers/CreateServer'; import type { ServersMap } from '../../src/servers/data'; import { checkAccessibility } from '../__helpers__/accessibility'; import { renderWithStore } from '../__helpers__/setUpTest'; @@ -24,17 +25,19 @@ describe('', () => { callCount += 1; return result; }); - const CreateServer = CreateServerFactory(fromPartial({ - ImportServersBtn: () => <>ImportServersBtn, - useTimeoutToggle, - })); const history = createMemoryHistory({ initialEntries: ['/foo', '/bar'] }); return { history, ...renderWithStore( - + <>ImportServersBtn, + useTimeoutToggle, + buildShlinkApiClient: vi.fn(), + })}> + + , { initialState: { servers }, @@ -64,11 +67,6 @@ describe('', () => { expect(screen.getByText('The servers could not be imported. Make sure the format is correct.')).toBeInTheDocument(); }); - it('shows import button when no servers exist yet', () => { - setUp({ servers: {} }); - expect(screen.queryByText('ImportServersBtn')).toBeInTheDocument(); - }); - it('creates server data when form is submitted', async () => { const { user, history, store } = setUp(); const expectedServerId = 'the_name-the_url.com'; diff --git a/test/servers/ManageServers.test.tsx b/test/servers/ManageServers.test.tsx index c3cebeab..f9395816 100644 --- a/test/servers/ManageServers.test.tsx +++ b/test/servers/ManageServers.test.tsx @@ -1,8 +1,9 @@ import { screen, waitFor } from '@testing-library/react'; import { fromPartial } from '@total-typescript/shoehorn'; import { MemoryRouter } from 'react-router'; +import { ContainerProvider } from '../../src/container/context'; import type { ServersMap, ServerWithId } from '../../src/servers/data'; -import { ManageServersFactory } from '../../src/servers/ManageServers'; +import { ManageServers } from '../../src/servers/ManageServers'; import type { ServersExporter } from '../../src/servers/services/ServersExporter'; import { checkAccessibility } from '../__helpers__/accessibility'; import { renderWithStore } from '../__helpers__/setUpTest'; @@ -11,16 +12,20 @@ describe('', () => { const exportServers = vi.fn(); const serversExporter = fromPartial({ exportServers }); const useTimeoutToggle = vi.fn().mockReturnValue([false, vi.fn()]); - const ManageServers = ManageServersFactory(fromPartial({ - ServersExporter: serversExporter, - ImportServersBtn: () => ImportServersBtn, - useTimeoutToggle, - })); const createServerMock = (value: string, autoConnect = false) => fromPartial( { id: value, name: value, url: value, autoConnect }, ); const setUp = (servers: ServersMap = {}) => renderWithStore( - , + + ImportServersBtn, + useTimeoutToggle, + buildShlinkApiClient: vi.fn(), + })}> + + + , { initialState: { servers }, }, diff --git a/test/servers/helpers/ImportServersBtn.test.tsx b/test/servers/helpers/ImportServersBtn.test.tsx index 06be5014..aa3a5cdd 100644 --- a/test/servers/helpers/ImportServersBtn.test.tsx +++ b/test/servers/helpers/ImportServersBtn.test.tsx @@ -1,9 +1,9 @@ import { screen, waitFor } from '@testing-library/react'; import { fromPartial } from '@total-typescript/shoehorn'; +import { ContainerProvider } from '../../../src/container/context'; import type { ServerData, ServersMap, ServerWithId } from '../../../src/servers/data'; -import type { - ImportServersBtnProps } from '../../../src/servers/helpers/ImportServersBtn'; -import { ImportServersBtnFactory } from '../../../src/servers/helpers/ImportServersBtn'; +import type { ImportServersBtnProps } from '../../../src/servers/helpers/ImportServersBtn'; +import { ImportServersBtn } from '../../../src/servers/helpers/ImportServersBtn'; import type { ServersImporter } from '../../../src/servers/services/ServersImporter'; import { checkAccessibility } from '../../__helpers__/accessibility'; import { renderWithStore } from '../../__helpers__/setUpTest'; @@ -13,9 +13,10 @@ describe('', () => { const onImportMock = vi.fn(); const importServersFromFile = vi.fn().mockResolvedValue([]); const serversImporterMock = fromPartial({ importServersFromFile }); - const ImportServersBtn = ImportServersBtnFactory(fromPartial({ ServersImporter: serversImporterMock })); const setUp = (props: Partial = {}, servers: ServersMap = {}) => renderWithStore( - , + + + , { initialState: { servers }, },