Refactor and fix main app tests

This commit is contained in:
Alejandro Celaya 2023-08-04 08:56:06 +02:00
parent c794ff8b58
commit c4d7ac272b
29 changed files with 162 additions and 160 deletions

View file

@ -3,4 +3,14 @@ import { createShlinkWebComponent } from './ShlinkWebComponent';
export const ShlinkWebComponent = createShlinkWebComponent(bottle); export const ShlinkWebComponent = createShlinkWebComponent(bottle);
export type { Settings } from './utils/settings'; export type ShlinkWebComponentType = typeof ShlinkWebComponent;
export type {
RealTimeUpdatesSettings,
ShortUrlCreationSettings,
ShortUrlsListSettings,
UiSettings,
VisitsSettings,
TagsSettings,
Settings,
} from './utils/settings';

View file

@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { SimplePaginator } from '../../shlink-web-component/src/utils/components/SimplePaginator'; import { SimplePaginator } from '../../../src/utils/components/SimplePaginator';
import { ELLIPSIS } from '../../shlink-web-component/src/utils/helpers/pagination'; import { ELLIPSIS } from '../../../src/utils/helpers/pagination';
describe('<SimplePaginator />', () => { describe('<SimplePaginator />', () => {
const setUp = (pagesCount: number, currentPage = 1) => render( const setUp = (pagesCount: number, currentPage = 1) => render(

View file

@ -109,7 +109,7 @@ export class ShlinkApiClient implements BaseShlinkApiClient {
.then(({ data }) => ({ tags: data.map(({ tag }) => tag), stats: data })); .then(({ data }) => ({ tags: data.map(({ tag }) => tag), stats: data }));
public readonly deleteTags = async (tags: string[]): Promise<{ tags: string[] }> => public readonly deleteTags = async (tags: string[]): Promise<{ tags: string[] }> =>
this.performEmptyRequest({ url: '/tags', method: 'DELETE', body: { tags } }).then(() => ({ tags })); this.performEmptyRequest({ url: '/tags', method: 'DELETE', query: { tags } }).then(() => ({ tags }));
public readonly editTag = async (oldName: string, newName: string): Promise<{ oldName: string; newName: string }> => public readonly editTag = async (oldName: string, newName: string): Promise<{ oldName: string; newName: string }> =>
this.performEmptyRequest({ this.performEmptyRequest({

View file

@ -21,7 +21,7 @@ interface AppProps {
export const App = ( export const App = (
MainHeader: FC, MainHeader: FC,
Home: FC, Home: FC,
MenuLayout: FC, ShlinkWebComponentContainer: FC,
CreateServer: FC, CreateServer: FC,
EditServer: FC, EditServer: FC,
SettingsComp: FC, SettingsComp: FC,
@ -52,7 +52,7 @@ export const App = (
<Route path="/manage-servers" element={<ManageServers />} /> <Route path="/manage-servers" element={<ManageServers />} />
<Route path="/server/create" element={<CreateServer />} /> <Route path="/server/create" element={<CreateServer />} />
<Route path="/server/:serverId/edit" element={<EditServer />} /> <Route path="/server/:serverId/edit" element={<EditServer />} />
<Route path="/server/:serverId/*" element={<MenuLayout />} /> <Route path="/server/:serverId/*" element={<ShlinkWebComponentContainer />} />
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
</Routes> </Routes>
</div> </div>

View file

@ -10,7 +10,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
App, App,
'MainHeader', 'MainHeader',
'Home', 'Home',
'MenuLayout', 'ShlinkWebComponentContainer',
'CreateServer', 'CreateServer',
'EditServer', 'EditServer',
'Settings', 'Settings',

View file

@ -1,33 +1,34 @@
import type { FC } from 'react'; import type { FC } from 'react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import type { Settings } from '../../shlink-web-component/src'; import type { Settings, ShlinkWebComponentType } from '../../shlink-web-component/src';
import { ShlinkWebComponent } from '../../shlink-web-component/src';
import type { ShlinkApiClientBuilder } from '../api/services/ShlinkApiClientBuilder'; import type { ShlinkApiClientBuilder } from '../api/services/ShlinkApiClientBuilder';
import { isReachableServer } from '../servers/data'; import { isReachableServer } from '../servers/data';
import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { withSelectedServer } from '../servers/helpers/withSelectedServer';
import { NotFound } from './NotFound'; import { NotFound } from './NotFound';
import './MenuLayout.scss'; import './ShlinkWebComponentContainer.scss';
interface MenuLayoutProps { interface ShlinkWebComponentContainerProps {
sidebarPresent: Function; sidebarPresent: Function;
sidebarNotPresent: Function; sidebarNotPresent: Function;
settings: Settings; settings: Settings;
} }
// FIXME Rename this to something else export const ShlinkWebComponentContainer = (
export const MenuLayout = (
buildShlinkApiClient: ShlinkApiClientBuilder, buildShlinkApiClient: ShlinkApiClientBuilder,
ShlinkWebComponent: ShlinkWebComponentType,
ServerError: FC, ServerError: FC,
) => withSelectedServer<MenuLayoutProps>(({ selectedServer, sidebarNotPresent, sidebarPresent, settings }) => { ) => withSelectedServer<ShlinkWebComponentContainerProps>((
const showContent = isReachableServer(selectedServer); { selectedServer, sidebarNotPresent, sidebarPresent, settings },
const routesPrefix = showContent ? `/server/${selectedServer.id}` : ''; ) => {
const selectedServerIsReachable = isReachableServer(selectedServer);
const routesPrefix = selectedServerIsReachable ? `/server/${selectedServer.id}` : '';
useEffect(() => { useEffect(() => {
showContent && sidebarPresent(); selectedServerIsReachable && sidebarPresent();
return () => sidebarNotPresent(); return () => sidebarNotPresent();
}, []); }, []);
if (!showContent) { if (!selectedServerIsReachable) {
return <ServerError />; return <ServerError />;
} }

View file

@ -1,8 +1,8 @@
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
// TODO This is only used for some components to have extra paddings/styles if existing section has a side menu // FIXME This is only used for some components to have extra paddings/styles if existing section has a side menu
// Now that's basically the route which renders ShlinkWebComponent, so maybe there's some way to re-think this // Now that's basically the route which renders ShlinkWebComponent, so maybe there's some way to re-think this
// logic, and perhaps get rid of a reducer just for that // logic, and perhaps get rid of a reducer just for that
export interface Sidebar { export interface Sidebar {
sidebarPresent: boolean; sidebarPresent: boolean;

View file

@ -1,13 +1,14 @@
import type Bottle from 'bottlejs'; import type Bottle from 'bottlejs';
import { ShlinkWebComponent } from '../../../shlink-web-component/src';
import type { ConnectDecorator } from '../../container/types'; import type { ConnectDecorator } from '../../container/types';
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer'; import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
import { ErrorHandler } from '../ErrorHandler'; import { ErrorHandler } from '../ErrorHandler';
import { Home } from '../Home'; import { Home } from '../Home';
import { MainHeader } from '../MainHeader'; import { MainHeader } from '../MainHeader';
import { MenuLayout } from '../MenuLayout';
import { sidebarNotPresent, sidebarPresent } from '../reducers/sidebar'; import { sidebarNotPresent, sidebarPresent } from '../reducers/sidebar';
import { ScrollToTop } from '../ScrollToTop'; import { ScrollToTop } from '../ScrollToTop';
import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer'; import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer';
import { ShlinkWebComponentContainer } from '../ShlinkWebComponentContainer';
import { HttpClient } from './HttpClient'; import { HttpClient } from './HttpClient';
export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
@ -26,8 +27,15 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.decorator('Home', withoutSelectedServer); bottle.decorator('Home', withoutSelectedServer);
bottle.decorator('Home', connect(['servers'], ['resetSelectedServer'])); bottle.decorator('Home', connect(['servers'], ['resetSelectedServer']));
bottle.serviceFactory('MenuLayout', MenuLayout, 'buildShlinkApiClient', 'ServerError'); bottle.serviceFactory('ShlinkWebComponent', () => ShlinkWebComponent);
bottle.decorator('MenuLayout', connect( bottle.serviceFactory(
'ShlinkWebComponentContainer',
ShlinkWebComponentContainer,
'buildShlinkApiClient',
'ShlinkWebComponent',
'ServerError',
);
bottle.decorator('ShlinkWebComponentContainer', connect(
['selectedServer', 'settings'], ['selectedServer', 'settings'],
['selectServer', 'sidebarPresent', 'sidebarNotPresent'], ['selectServer', 'sidebarPresent', 'sidebarNotPresent'],
)); ));

View file

@ -1,11 +1,10 @@
import type { FC, ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
import { DropdownItem, FormGroup } from 'reactstrap'; import { DropdownItem, FormGroup } from 'reactstrap';
import { DropdownBtn, LabeledFormGroup, SimpleCard, ToggleSwitch } from '../../shlink-frontend-kit/src'; import { DropdownBtn, LabeledFormGroup, SimpleCard, ToggleSwitch } from '../../shlink-frontend-kit/src';
import type { Settings } from '../../shlink-web-component/src'; import type { Settings, ShortUrlCreationSettings as ShortUrlsSettings } from '../../shlink-web-component/src';
import { FormText } from '../utils/forms/FormText'; import { FormText } from '../utils/forms/FormText';
import type { Defined } from '../utils/types'; import type { Defined } from '../utils/types';
type ShortUrlsSettings = Defined<Settings['shortUrlCreation']>;
type TagFilteringMode = Defined<ShortUrlsSettings['tagFilteringMode']>; type TagFilteringMode = Defined<ShortUrlsSettings['tagFilteringMode']>;
interface ShortUrlCreationProps { interface ShortUrlCreationProps {

View file

@ -1,12 +1,9 @@
import type { FC } from 'react'; import type { FC } from 'react';
import { LabeledFormGroup, OrderingDropdown, SimpleCard } from '../../shlink-frontend-kit/src'; import { LabeledFormGroup, OrderingDropdown, SimpleCard } from '../../shlink-frontend-kit/src';
import type { Settings } from '../../shlink-web-component/src'; import type { Settings, ShortUrlsListSettings as ShortUrlsSettings } from '../../shlink-web-component/src';
import { SHORT_URLS_ORDERABLE_FIELDS } from '../../shlink-web-component/src/short-urls/data'; import { SHORT_URLS_ORDERABLE_FIELDS } from '../../shlink-web-component/src/short-urls/data';
import type { Defined } from '../utils/types';
import { DEFAULT_SHORT_URLS_ORDERING } from './reducers/settings'; import { DEFAULT_SHORT_URLS_ORDERING } from './reducers/settings';
type ShortUrlsSettings = Defined<Settings['shortUrlsList']>;
interface ShortUrlsListSettingsProps { interface ShortUrlsListSettingsProps {
settings: Settings; settings: Settings;
setShortUrlsListSettings: (settings: ShortUrlsSettings) => void; setShortUrlsListSettings: (settings: ShortUrlsSettings) => void;

View file

@ -1,10 +1,7 @@
import type { FC } from 'react'; import type { FC } from 'react';
import { LabeledFormGroup, OrderingDropdown, SimpleCard } from '../../shlink-frontend-kit/src'; import { LabeledFormGroup, OrderingDropdown, SimpleCard } from '../../shlink-frontend-kit/src';
import type { Settings } from '../../shlink-web-component/src'; import type { Settings, TagsSettings as TagsSettingsOptions } from '../../shlink-web-component/src';
import { TAGS_ORDERABLE_FIELDS } from '../../shlink-web-component/src/tags/data/TagsListChildrenProps'; import { TAGS_ORDERABLE_FIELDS } from '../../shlink-web-component/src/tags/data/TagsListChildrenProps';
import type { Defined } from '../utils/types';
type TagsSettingsOptions = Defined<Settings['tags']>;
interface TagsProps { interface TagsProps {
settings: Settings; settings: Settings;

View file

@ -2,14 +2,11 @@ import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC } from 'react'; import type { FC } from 'react';
import { SimpleCard, ToggleSwitch } from '../../shlink-frontend-kit/src'; import { SimpleCard, ToggleSwitch } from '../../shlink-frontend-kit/src';
import type { Settings } from '../../shlink-web-component/src'; import type { Settings, UiSettings } from '../../shlink-web-component/src';
import type { Theme } from '../utils/theme'; import type { Theme } from '../utils/theme';
import { changeThemeInMarkup } from '../utils/theme'; import { changeThemeInMarkup } from '../utils/theme';
import type { Defined } from '../utils/types';
import './UserInterfaceSettings.scss'; import './UserInterfaceSettings.scss';
type UiSettings = Defined<Settings['ui']>;
interface UserInterfaceProps { interface UserInterfaceProps {
settings: Settings; settings: Settings;
setUiSettings: (settings: UiSettings) => void; setUiSettings: (settings: UiSettings) => void;

View file

@ -1,13 +1,11 @@
import type { FC } from 'react'; import type { FC } from 'react';
import { FormGroup } from 'reactstrap'; import { FormGroup } from 'reactstrap';
import { LabeledFormGroup, SimpleCard, ToggleSwitch } from '../../shlink-frontend-kit/src'; import { LabeledFormGroup, SimpleCard, ToggleSwitch } from '../../shlink-frontend-kit/src';
import type { Settings } from '../../shlink-web-component/src'; import type { Settings, VisitsSettings as VisitsSettingsConfig } from '../../shlink-web-component/src';
import type { DateInterval } from '../../shlink-web-component/src/utils/dates/helpers/dateIntervals'; import type { DateInterval } from '../../shlink-web-component/src/utils/dates/helpers/dateIntervals';
import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector'; import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector';
import { FormText } from '../utils/forms/FormText'; import { FormText } from '../utils/forms/FormText';
type VisitsSettingsConfig = Settings['visits'];
interface VisitsProps { interface VisitsProps {
settings: Settings; settings: Settings;
setVisitsSettings: (settings: VisitsSettingsConfig) => void; setVisitsSettings: (settings: VisitsSettingsConfig) => void;

View file

@ -1,10 +1,15 @@
import type { PayloadAction, PrepareAction } from '@reduxjs/toolkit'; import type { PayloadAction, PrepareAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import { mergeDeepRight } from 'ramda'; import { mergeDeepRight } from 'ramda';
import type { Settings } from '../../../shlink-web-component/src'; import type {
Settings,
ShortUrlCreationSettings,
ShortUrlsListSettings,
TagsSettings,
UiSettings, VisitsSettings } from '../../../shlink-web-component/src';
import type { Defined } from '../../utils/types'; import type { Defined } from '../../utils/types';
type ShortUrlsOrder = Defined<Defined<Settings['shortUrlsList']>['defaultOrdering']>; type ShortUrlsOrder = Defined<ShortUrlsListSettings['defaultOrdering']>;
export const DEFAULT_SHORT_URLS_ORDERING: ShortUrlsOrder = { export const DEFAULT_SHORT_URLS_ORDERING: ShortUrlsOrder = {
field: 'dateCreated', field: 'dateCreated',
@ -43,14 +48,14 @@ const { reducer, actions } = createSlice({
toggleRealTimeUpdates: toReducer((enabled: boolean) => toPreparedAction({ realTimeUpdates: { enabled } })), toggleRealTimeUpdates: toReducer((enabled: boolean) => toPreparedAction({ realTimeUpdates: { enabled } })),
setRealTimeUpdatesInterval: toReducer((interval: number) => toPreparedAction({ realTimeUpdates: { interval } })), setRealTimeUpdatesInterval: toReducer((interval: number) => toPreparedAction({ realTimeUpdates: { interval } })),
setShortUrlCreationSettings: toReducer( setShortUrlCreationSettings: toReducer(
(shortUrlCreation: Settings['shortUrlCreation']) => toPreparedAction({ shortUrlCreation }), (shortUrlCreation: ShortUrlCreationSettings) => toPreparedAction({ shortUrlCreation }),
), ),
setShortUrlsListSettings: toReducer( setShortUrlsListSettings: toReducer(
(shortUrlsList: Settings['shortUrlsList']) => toPreparedAction({ shortUrlsList }), (shortUrlsList: ShortUrlsListSettings) => toPreparedAction({ shortUrlsList }),
), ),
setUiSettings: toReducer((ui: Settings['ui']) => toPreparedAction({ ui })), setUiSettings: toReducer((ui: UiSettings) => toPreparedAction({ ui })),
setVisitsSettings: toReducer((visits: Settings['visits']) => toPreparedAction({ visits })), setVisitsSettings: toReducer((visits: VisitsSettings) => toPreparedAction({ visits })),
setTagsSettings: toReducer((tags: Settings['tags']) => toPreparedAction({ tags })), setTagsSettings: toReducer((tags: TagsSettings) => toPreparedAction({ tags })),
}, },
}); });

View file

@ -1,11 +1,10 @@
import type { FC } from 'react'; import type { FC } from 'react';
import { DropdownItem } from 'reactstrap'; import { DropdownItem } from 'reactstrap';
import { DropdownBtn } from '../../../shlink-frontend-kit/src'; import { DropdownBtn } from '../../../shlink-frontend-kit/src';
import type { Settings } from '../../../shlink-web-component/src'; import type { VisitsSettings } from '../../../shlink-web-component/src';
import { rangeOrIntervalToString } from '../../../shlink-web-component/src/utils/dates/helpers/dateIntervals'; import { rangeOrIntervalToString } from '../../../shlink-web-component/src/utils/dates/helpers/dateIntervals';
import type { Defined } from '../types';
export type DateInterval = Defined<Settings['visits']>['defaultInterval']; export type DateInterval = VisitsSettings['defaultInterval'];
export interface DateIntervalSelectorProps { export interface DateIntervalSelectorProps {
active?: DateInterval; active?: DateInterval;

View file

@ -1,7 +1,7 @@
import { compare } from 'compare-versions'; import { compare } from 'compare-versions';
import { identity, isEmpty, isNil, memoizeWith } from 'ramda'; import { identity, isEmpty, isNil, memoizeWith } from 'ramda';
type Empty = null | undefined | '' | never[]; export type Empty = null | undefined | '' | never[];
const hasValue = <T>(value: T | Empty): value is T => !isNil(value) && !isEmpty(value); const hasValue = <T>(value: T | Empty): value is T => !isNil(value) && !isEmpty(value);
@ -11,7 +11,7 @@ type SemVerPattern = SemVerPatternFragment
| `${SemVerPatternFragment}.${SemVerPatternFragment}` | `${SemVerPatternFragment}.${SemVerPatternFragment}`
| `${SemVerPatternFragment}.${SemVerPatternFragment}.${SemVerPatternFragment}`; | `${SemVerPatternFragment}.${SemVerPatternFragment}.${SemVerPatternFragment}`;
type Versions = { export type Versions = {
maxVersion?: SemVerPattern; maxVersion?: SemVerPattern;
minVersion?: SemVerPattern; minVersion?: SemVerPattern;
}; };

View file

@ -1,8 +1,8 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkDomain, ShlinkVisits, ShlinkVisitsOverview } from '../../../shlink-web-component/src/api-contract';
import { ErrorTypeV2, ErrorTypeV3 } from '../../../shlink-web-component/src/api-contract';
import type { ShortUrl, ShortUrlsOrder } from '../../../shlink-web-component/src/short-urls/data'; import type { ShortUrl, ShortUrlsOrder } from '../../../shlink-web-component/src/short-urls/data';
import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient'; import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
import type { ShlinkDomain, ShlinkVisits, ShlinkVisitsOverview } from '../../../src/api/types';
import { ErrorTypeV2, ErrorTypeV3 } from '../../../src/api/types/errors';
import type { HttpClient } from '../../../src/common/services/HttpClient'; import type { HttpClient } from '../../../src/common/services/HttpClient';
import type { OptionalString } from '../../../src/utils/utils'; import type { OptionalString } from '../../../src/utils/utils';

View file

@ -8,7 +8,7 @@ describe('<App />', () => {
const App = createApp( const App = createApp(
() => <>MainHeader</>, () => <>MainHeader</>,
() => <>Home</>, () => <>Home</>,
() => <>MenuLayout</>, () => <>ShlinkWebComponentContainer</>,
() => <>CreateServer</>, () => <>CreateServer</>,
() => <>EditServer</>, () => <>EditServer</>,
() => <>SettingsComp</>, () => <>SettingsComp</>,
@ -47,8 +47,8 @@ describe('<App />', () => {
['/server/create', 'CreateServer'], ['/server/create', 'CreateServer'],
['/server/abc123/edit', 'EditServer'], ['/server/abc123/edit', 'EditServer'],
['/server/def456/edit', 'EditServer'], ['/server/def456/edit', 'EditServer'],
['/server/abc123/foo', 'MenuLayout'], ['/server/abc123/foo', 'ShlinkWebComponentContainer'],
['/server/def456/bar', 'MenuLayout'], ['/server/def456/bar', 'ShlinkWebComponentContainer'],
['/other', 'Oops! We could not find requested route.'], ['/other', 'Oops! We could not find requested route.'],
])('renders expected route', async (activeRoute, expectedComponent) => { ])('renders expected route', async (activeRoute, expectedComponent) => {
setUp(activeRoute); setUp(activeRoute);

View file

@ -1,88 +0,0 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { createMemoryHistory } from 'history';
import { Router, useParams } from 'react-router-dom';
import { MenuLayout as createMenuLayout } from '../../src/common/MenuLayout';
import type { NonReachableServer, NotFoundServer, SelectedServer } from '../../src/servers/data';
import type { SemVer } from '../../src/utils/helpers/version';
vi.mock('react-router-dom', async () => ({
...(await vi.importActual<any>('react-router-dom')),
useParams: vi.fn(),
}));
describe('<MenuLayout />', () => {
const MenuLayout = createMenuLayout(
() => <>TagsList</>,
() => <>ShortUrlsList</>,
() => <>CreateShortUrl</>,
() => <>ShortUrlVisits</>,
() => <>TagVisits</>,
() => <>DomainVisits</>,
() => <>OrphanVisits</>,
() => <>NonOrphanVisits</>,
() => <>ServerError</>,
() => <>OverviewRoute</>,
() => <>EditShortUrl</>,
() => <>ManageDomains</>,
);
const setUp = (selectedServer: SelectedServer, currentPath = '/') => {
const history = createMemoryHistory();
history.push(currentPath);
return render(
<Router location={history.location} navigator={history}>
<MenuLayout
sidebarNotPresent={vi.fn()}
sidebarPresent={vi.fn()}
selectServer={vi.fn()}
selectedServer={selectedServer}
/>
</Router>,
);
};
beforeEach(() => {
(useParams as any).mockReturnValue({ serverId: 'abc123' });
});
it('shows loading indicator while loading server', () => {
setUp(null);
expect(screen.getByText('Loading...')).toBeInTheDocument();
expect(screen.queryByText('ServerError')).not.toBeInTheDocument();
});
it.each([
[fromPartial<NotFoundServer>({ serverNotFound: true })],
[fromPartial<NonReachableServer>({ serverNotReachable: true })],
])('shows error for non reachable servers', (selectedServer) => {
setUp(selectedServer);
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
expect(screen.getByText('ServerError')).toBeInTheDocument();
});
it.each([
['3.0.0' as SemVer, '/overview', 'OverviewRoute'],
['3.0.0' as SemVer, '/list-short-urls/1', 'ShortUrlsList'],
['3.0.0' as SemVer, '/create-short-url', 'CreateShortUrl'],
['3.0.0' as SemVer, '/short-code/abc123/visits/foo', 'ShortUrlVisits'],
['3.0.0' as SemVer, '/short-code/abc123/edit', 'EditShortUrl'],
['3.0.0' as SemVer, '/tag/foo/visits/foo', 'TagVisits'],
['3.0.0' as SemVer, '/orphan-visits/foo', 'OrphanVisits'],
['3.0.0' as SemVer, '/manage-tags', 'TagsList'],
['3.0.0' as SemVer, '/not-found', 'Oops! We could not find requested route.'],
['3.0.0' as SemVer, '/domain/domain.com/visits/foo', 'Oops! We could not find requested route.'],
['3.1.0' as SemVer, '/domain/domain.com/visits/foo', 'DomainVisits'],
['2.10.0' as SemVer, '/non-orphan-visits/foo', 'Oops! We could not find requested route.'],
['3.0.0' as SemVer, '/non-orphan-visits/foo', 'NonOrphanVisits'],
['2.8.0' as SemVer, '/manage-domains', 'ManageDomains'],
])(
'renders expected component based on location and server version',
(version, currentPath, expectedContent) => {
setUp(fromPartial({ version }), currentPath);
expect(screen.getByText(expectedContent)).toBeInTheDocument();
},
);
});

View file

@ -0,0 +1,82 @@
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 type { NonReachableServer, NotFoundServer, SelectedServer } from '../../src/servers/data';
vi.mock('react-router-dom', async () => ({
...(await vi.importActual<any>('react-router-dom')),
useParams: vi.fn(),
}));
describe('<ShlinkWebComponentContainer />', () => {
const ShlinkWebComponentContainer = createContainer(
vi.fn().mockReturnValue(fromPartial({})),
() => <>ShlinkWebComponent</>,
() => <>ServerError</>,
);
const setUp = (selectedServer: SelectedServer) => render(
<ShlinkWebComponentContainer
sidebarNotPresent={vi.fn()}
sidebarPresent={vi.fn()}
selectServer={vi.fn()}
selectedServer={selectedServer}
settings={{}}
/>,
);
beforeEach(() => {
(useParams as any).mockReturnValue({ serverId: 'abc123' });
});
it('shows loading indicator while loading server', () => {
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) => {
setUp(selectedServer);
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
expect(screen.getByText('ServerError')).toBeInTheDocument();
expect(screen.queryByText('ShlinkWebComponent')).not.toBeInTheDocument();
});
it('renders ShlinkWebComponent for reachable servers', () => {
setUp(fromPartial({ version: '3.0.0' }));
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
expect(screen.queryByText('ServerError')).not.toBeInTheDocument();
expect(screen.getByText('ShlinkWebComponent')).toBeInTheDocument();
});
// FIXME Move this case to ShlinkWebComponent test
// it.each([
// ['3.0.0' as SemVer, '/overview', 'OverviewRoute'],
// ['3.0.0' as SemVer, '/list-short-urls/1', 'ShortUrlsList'],
// ['3.0.0' as SemVer, '/create-short-url', 'CreateShortUrl'],
// ['3.0.0' as SemVer, '/short-code/abc123/visits/foo', 'ShortUrlVisits'],
// ['3.0.0' as SemVer, '/short-code/abc123/edit', 'EditShortUrl'],
// ['3.0.0' as SemVer, '/tag/foo/visits/foo', 'TagVisits'],
// ['3.0.0' as SemVer, '/orphan-visits/foo', 'OrphanVisits'],
// ['3.0.0' as SemVer, '/manage-tags', 'TagsList'],
// ['3.0.0' as SemVer, '/not-found', 'Oops! We could not find requested route.'],
// ['3.0.0' as SemVer, '/domain/domain.com/visits/foo', 'Oops! We could not find requested route.'],
// ['3.1.0' as SemVer, '/domain/domain.com/visits/foo', 'DomainVisits'],
// ['2.10.0' as SemVer, '/non-orphan-visits/foo', 'Oops! We could not find requested route.'],
// ['3.0.0' as SemVer, '/non-orphan-visits/foo', 'NonOrphanVisits'],
// ['2.8.0' as SemVer, '/manage-domains', 'ManageDomains'],
// ])(
// 'renders expected component based on location and server version',
// (version, currentPath, expectedContent) => {
// setUp(fromPartial({ version }), currentPath);
// expect(screen.getByText(expectedContent)).toBeInTheDocument();
// },
// );
});

View file

@ -58,11 +58,11 @@ describe('<ManageServers />', () => {
expect(screen.getAllByRole('columnheader')).toHaveLength(expectedCols); expect(screen.getAllByRole('columnheader')).toHaveLength(expectedCols);
if (server.autoConnect) { if (server.autoConnect) {
expect(screen.getByText(/\[YES\]/)).toBeInTheDocument(); expect(screen.getByText(/\[YES]/)).toBeInTheDocument();
expect(screen.queryByText(/\[NO\]/)).not.toBeInTheDocument(); expect(screen.queryByText(/\[NO]/)).not.toBeInTheDocument();
} else { } else {
expect(screen.queryByText(/\[YES\]/)).not.toBeInTheDocument(); expect(screen.queryByText(/\[YES]/)).not.toBeInTheDocument();
expect(screen.getByText(/\[NO\]/)).toBeInTheDocument(); expect(screen.getByText(/\[NO]/)).toBeInTheDocument();
} }
}); });

View file

@ -1,9 +1,7 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { RealTimeUpdatesSettings as RealTimeUpdatesSettingsOptions } from '../../shlink-web-component/src';
import { RealTimeUpdatesSettings } from '../../src/settings/RealTimeUpdatesSettings'; import { RealTimeUpdatesSettings } from '../../src/settings/RealTimeUpdatesSettings';
import type {
RealTimeUpdatesSettings as RealTimeUpdatesSettingsOptions,
} from '../../src/settings/reducers/settings';
import { renderWithEvents } from '../__helpers__/setUpTest'; import { renderWithEvents } from '../__helpers__/setUpTest';
describe('<RealTimeUpdatesSettings />', () => { describe('<RealTimeUpdatesSettings />', () => {

View file

@ -1,6 +1,6 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShortUrlCreationSettings as ShortUrlsSettings } from '../../src/settings/reducers/settings'; import type { ShortUrlCreationSettings as ShortUrlsSettings } from '../../shlink-web-component/src';
import { ShortUrlCreationSettings } from '../../src/settings/ShortUrlCreationSettings'; import { ShortUrlCreationSettings } from '../../src/settings/ShortUrlCreationSettings';
import { renderWithEvents } from '../__helpers__/setUpTest'; import { renderWithEvents } from '../__helpers__/setUpTest';

View file

@ -1,7 +1,7 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { ShortUrlsListSettings as ShortUrlsSettings } from '../../shlink-web-component/src';
import type { ShortUrlsOrder } from '../../shlink-web-component/src/short-urls/data'; import type { ShortUrlsOrder } from '../../shlink-web-component/src/short-urls/data';
import type { ShortUrlsListSettings as ShortUrlsSettings } from '../../src/settings/reducers/settings';
import { ShortUrlsListSettings } from '../../src/settings/ShortUrlsListSettings'; import { ShortUrlsListSettings } from '../../src/settings/ShortUrlsListSettings';
import { renderWithEvents } from '../__helpers__/setUpTest'; import { renderWithEvents } from '../__helpers__/setUpTest';

View file

@ -1,7 +1,7 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { TagsSettings as TagsSettingsOptions } from '../../shlink-web-component/src';
import type { TagsOrder } from '../../shlink-web-component/src/tags/data/TagsListChildrenProps'; import type { TagsOrder } from '../../shlink-web-component/src/tags/data/TagsListChildrenProps';
import type { TagsSettings as TagsSettingsOptions } from '../../src/settings/reducers/settings';
import { TagsSettings } from '../../src/settings/TagsSettings'; import { TagsSettings } from '../../src/settings/TagsSettings';
import { renderWithEvents } from '../__helpers__/setUpTest'; import { renderWithEvents } from '../__helpers__/setUpTest';

View file

@ -1,6 +1,6 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { UiSettings } from '../../src/settings/reducers/settings'; import type { UiSettings } from '../../shlink-web-component/src';
import { UserInterfaceSettings } from '../../src/settings/UserInterfaceSettings'; import { UserInterfaceSettings } from '../../src/settings/UserInterfaceSettings';
import type { Theme } from '../../src/utils/theme'; import type { Theme } from '../../src/utils/theme';
import { renderWithEvents } from '../__helpers__/setUpTest'; import { renderWithEvents } from '../__helpers__/setUpTest';

View file

@ -1,6 +1,6 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { Settings } from '../../src/settings/reducers/settings'; import type { Settings } from '../../shlink-web-component/src';
import { VisitsSettings } from '../../src/settings/VisitsSettings'; import { VisitsSettings } from '../../src/settings/VisitsSettings';
import { renderWithEvents } from '../__helpers__/setUpTest'; import { renderWithEvents } from '../__helpers__/setUpTest';

View file

@ -1,7 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { SemVer, Versions } from '../../../src/utils/helpers/version'; import type { Empty, SemVer, Versions } from '../../../src/utils/helpers/version';
import { versionMatch } from '../../../src/utils/helpers/version'; import { versionMatch } from '../../../src/utils/helpers/version';
import type { Empty } from '../../../src/utils/utils';
describe('version', () => { describe('version', () => {
describe('versionMatch', () => { describe('versionMatch', () => {