Fix shlink-web-component tests

This commit is contained in:
Alejandro Celaya 2023-08-04 11:16:01 +02:00
parent bdcfcee60e
commit 4d8477a32c
54 changed files with 345 additions and 431 deletions

View file

@ -14,7 +14,7 @@ export const VisitsHighlightCard: FC<VisitsHighlightCardProps> = ({ loading, exc
<HighlightCard
tooltip={
visitsSummary.bots !== undefined
? <>{excludeBots ? 'Plus' : 'Including'} <b>{prettify(visitsSummary.bots)}</b> potential bot visits</>
? <>{excludeBots ? 'Plus' : 'Including'} <strong>{prettify(visitsSummary.bots)}</strong> potential bot visits</>
: undefined
}
{...rest}

View file

@ -1,5 +1,5 @@
import type { FC, ReactElement } from 'react';
import { useToggle } from '../../src/utils/helpers/hooks';
import { useToggle } from '../../../shlink-frontend-kit/src';
interface RenderModalArgs {
isOpen: boolean;

View file

@ -1,12 +1,11 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router';
import { AsideMenu } from '../../src/common/AsideMenu';
describe('<AsideMenu />', () => {
const setUp = () => render(
<MemoryRouter>
<AsideMenu selectedServer={fromPartial({ id: 'abc123', version: '2.8.0' })} />
<AsideMenu routePrefix="/abc123" />
</MemoryRouter>,
);

View file

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import type { InvalidArgumentError, ProblemDetailsError } from '../../src/api/types/errors';
import { ErrorTypeV2, ErrorTypeV3 } from '../../src/api/types/errors';
import type { InvalidArgumentError, ProblemDetailsError } from '../../src/api-contract';
import { ErrorTypeV2, ErrorTypeV3 } from '../../src/api-contract';
import type { ShlinkApiErrorProps } from '../../src/common/ShlinkApiError';
import { ShlinkApiError } from '../../src/common/ShlinkApiError';

View file

@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkDomainRedirects } from '../../src/api/types';
import type { ShlinkDomainRedirects } from '../../src/api-contract';
import type { Domain } from '../../src/domains/data';
import { DomainRow } from '../../src/domains/DomainRow';
@ -21,7 +21,6 @@ describe('<DomainRow />', () => {
<DomainRow
domain={domain}
defaultRedirects={defaultRedirects}
selectedServer={fromPartial({})}
editDomainRedirects={vi.fn()}
checkDomainHealth={vi.fn()}
/>

View file

@ -1,26 +1,29 @@
import { screen, waitForElementToBeRemoved } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { SelectedServer } from '../../../../src/servers/data';
import type { SemVer } from '../../../../src/utils/helpers/version';
import type { Domain } from '../../../src/domains/data';
import { DomainDropdown } from '../../../src/domains/helpers/DomainDropdown';
import { FeaturesProvider } from '../../../src/utils/features';
import { RoutesPrefixProvider } from '../../../src/utils/routesPrefix';
import { renderWithEvents } from '../../__helpers__/setUpTest';
describe('<DomainDropdown />', () => {
const editDomainRedirects = vi.fn().mockResolvedValue(undefined);
const setUp = (domain?: Domain, selectedServer?: SelectedServer) => renderWithEvents(
const setUp = ({ domain, withVisits = true }: { domain?: Domain; withVisits?: boolean } = {}) => renderWithEvents(
<MemoryRouter>
<DomainDropdown
domain={domain ?? fromPartial({})}
selectedServer={selectedServer ?? fromPartial({})}
editDomainRedirects={editDomainRedirects}
/>
<RoutesPrefixProvider value="/server/123">
<FeaturesProvider value={fromPartial({ domainVisits: withVisits })}>
<DomainDropdown
domain={domain ?? fromPartial({})}
editDomainRedirects={editDomainRedirects}
/>
</FeaturesProvider>
</RoutesPrefixProvider>
</MemoryRouter>,
);
it('renders expected menu items', () => {
setUp();
setUp({ withVisits: false });
expect(screen.queryByText('Visit stats')).not.toBeInTheDocument();
expect(screen.getByText('Edit redirects')).toBeInTheDocument();
@ -30,37 +33,17 @@ describe('<DomainDropdown />', () => {
[true, '_DEFAULT'],
[false, ''],
])('points first link to the proper section', (isDefault, expectedLink) => {
setUp(
fromPartial({ domain: 'foo.com', isDefault }),
fromPartial({ version: '3.1.0', id: '123' }),
);
setUp({ domain: fromPartial({ domain: 'foo.com', isDefault }) });
expect(screen.getByText('Visit stats')).toHaveAttribute('href', `/server/123/domain/foo.com${expectedLink}/visits`);
});
it.each([
[true, '2.9.0' as SemVer, false],
[true, '2.10.0' as SemVer, true],
[false, '2.9.0' as SemVer, true],
])('allows editing certain the domains', (isDefault, serverVersion, canBeEdited) => {
setUp(
fromPartial({ domain: 'foo.com', isDefault }),
fromPartial({ version: serverVersion, id: '123' }),
);
if (canBeEdited) {
expect(screen.getByText('Edit redirects')).not.toHaveAttribute('disabled');
} else {
expect(screen.getByText('Edit redirects')).toHaveAttribute('disabled');
}
});
it.each([
['foo.com'],
['bar.org'],
['baz.net'],
])('displays modal when editing redirects', async (domain) => {
const { user } = setUp(fromPartial({ domain, isDefault: false }));
const { user } = setUp({ domain: fromPartial({ domain, isDefault: false }) });
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
expect(screen.queryByRole('form')).not.toBeInTheDocument();

View file

@ -1,6 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkDomainRedirects } from '../../../src/api/types';
import type { ShlinkDomainRedirects } from '../../../src/api-contract';
import { editDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
describe('domainRedirectsReducer', () => {

View file

@ -1,8 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import type { ShlinkDomainRedirects } from '../../../src/api/types';
import { parseApiError } from '../../../src/api/utils';
import type { ShlinkApiClient, ShlinkDomainRedirects } from '../../../src/api-contract';
import { parseApiError } from '../../../src/api-contract/utils';
import type { Domain } from '../../../src/domains/data';
import type { EditDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
import { editDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
@ -17,35 +15,35 @@ describe('domainsListReducer', () => {
const getState = vi.fn();
const listDomains = vi.fn();
const health = vi.fn();
const buildShlinkApiClient = () => fromPartial<ShlinkApiClient>({ listDomains, health });
const apiClientFactory = () => fromPartial<ShlinkApiClient>({ listDomains, health });
const filteredDomains: Domain[] = [
fromPartial({ domain: 'foo', status: 'validating' }),
fromPartial({ domain: 'Boo', status: 'validating' }),
];
const domains: Domain[] = [...filteredDomains, fromPartial({ domain: 'bar', status: 'validating' })];
const error = { type: 'NOT_FOUND', status: 404 } as unknown as Error;
const editDomainRedirectsThunk = editDomainRedirects(buildShlinkApiClient);
const editDomainRedirectsThunk = editDomainRedirects(apiClientFactory);
const { reducer, listDomains: listDomainsAction, checkDomainHealth, filterDomains } = domainsListReducerCreator(
buildShlinkApiClient,
apiClientFactory,
editDomainRedirectsThunk,
);
describe('reducer', () => {
it('returns loading on LIST_DOMAINS_START', () => {
expect(reducer(undefined, listDomainsAction.pending(''))).toEqual(
expect(reducer(undefined, listDomainsAction.pending('', {}))).toEqual(
{ domains: [], filteredDomains: [], loading: true, error: false },
);
});
it('returns error on LIST_DOMAINS_ERROR', () => {
expect(reducer(undefined, listDomainsAction.rejected(error, ''))).toEqual(
expect(reducer(undefined, listDomainsAction.rejected(error, '', {}))).toEqual(
{ domains: [], filteredDomains: [], loading: false, error: true, errorData: parseApiError(error) },
);
});
it('returns domains on LIST_DOMAINS', () => {
expect(
reducer(undefined, listDomainsAction.fulfilled({ domains }, '')),
reducer(undefined, listDomainsAction.fulfilled({ domains }, '', {})),
).toEqual({ domains, filteredDomains: domains, loading: false, error: false });
});
@ -93,7 +91,7 @@ describe('domainsListReducer', () => {
it('dispatches domains once loaded', async () => {
listDomains.mockResolvedValue({ data: domains });
await listDomainsAction()(dispatch, getState, {});
await listDomainsAction({})(dispatch, getState, {});
expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenLastCalledWith(expect.objectContaining({
@ -116,33 +114,13 @@ describe('domainsListReducer', () => {
describe('checkDomainHealth', () => {
const domain = 'example.com';
it('dispatches invalid status when selected server does not have all required data', async () => {
getState.mockReturnValue(fromPartial<ShlinkState>({
selectedServer: {},
}));
await checkDomainHealth(domain)(dispatch, getState, {});
expect(getState).toHaveBeenCalledTimes(1);
expect(health).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenLastCalledWith(expect.objectContaining({
payload: { domain, status: 'invalid' },
}));
});
it('dispatches invalid status when health endpoint returns an error', async () => {
getState.mockReturnValue(fromPartial<ShlinkState>({
selectedServer: {
url: 'https://myerver.com',
apiKey: '123',
},
}));
health.mockRejectedValue({});
await checkDomainHealth(domain)(dispatch, getState, {});
expect(getState).toHaveBeenCalledTimes(1);
expect(health).toHaveBeenCalledTimes(1);
expect(health).toHaveBeenCalledWith(domain);
expect(dispatch).toHaveBeenLastCalledWith(expect.objectContaining({
payload: { domain, status: 'invalid' },
}));
@ -155,18 +133,12 @@ describe('domainsListReducer', () => {
healthStatus,
expectedStatus,
) => {
getState.mockReturnValue(fromPartial<ShlinkState>({
selectedServer: {
url: 'https://myerver.com',
apiKey: '123',
},
}));
health.mockResolvedValue({ status: healthStatus });
await checkDomainHealth(domain)(dispatch, getState, {});
expect(getState).toHaveBeenCalledTimes(1);
expect(health).toHaveBeenCalledTimes(1);
expect(health).toHaveBeenCalledWith(domain);
expect(dispatch).toHaveBeenLastCalledWith(expect.objectContaining({
payload: { domain, status: expectedStatus },
}));

View file

@ -1,6 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { GetState } from '../../../../src/container/types';
import type { Settings } from '../../../src';
import type { ShlinkApiClient } from '../../../src/api-contract';
import { mercureInfoReducerCreator } from '../../../src/mercure/reducers/mercureInfo';
describe('mercureInfoReducer', () => {
@ -14,21 +14,21 @@ describe('mercureInfoReducer', () => {
describe('reducer', () => {
it('returns loading on GET_MERCURE_INFO_START', () => {
expect(reducer(undefined, loadMercureInfo.pending(''))).toEqual({
expect(reducer(undefined, loadMercureInfo.pending('', {}))).toEqual({
loading: true,
error: false,
});
});
it('returns error on GET_MERCURE_INFO_ERROR', () => {
expect(reducer(undefined, loadMercureInfo.rejected(null, ''))).toEqual({
expect(reducer(undefined, loadMercureInfo.rejected(null, '', {}))).toEqual({
loading: false,
error: true,
});
});
it('returns mercure info on GET_MERCURE_INFO', () => {
expect(reducer(undefined, loadMercureInfo.fulfilled(mercureInfo, ''))).toEqual(
expect(reducer(undefined, loadMercureInfo.fulfilled(mercureInfo, '', {}))).toEqual(
expect.objectContaining({ ...mercureInfo, loading: false, error: false }),
);
});
@ -36,17 +36,15 @@ describe('mercureInfoReducer', () => {
describe('loadMercureInfo', () => {
const dispatch = vi.fn();
const createGetStateMock = (enabled: boolean): GetState => vi.fn().mockReturnValue({
settings: {
realTimeUpdates: { enabled },
},
const createSettings = (enabled: boolean): Settings => fromPartial({
realTimeUpdates: { enabled },
});
it('dispatches error when real time updates are disabled', async () => {
getMercureInfo.mockResolvedValue(mercureInfo);
const getState = createGetStateMock(false);
const settings = createSettings(false);
await loadMercureInfo()(dispatch, getState, {});
await loadMercureInfo(settings)(dispatch, vi.fn(), {});
expect(getMercureInfo).not.toHaveBeenCalled();
expect(dispatch).toHaveBeenCalledTimes(2);
@ -57,9 +55,9 @@ describe('mercureInfoReducer', () => {
it('calls API on success', async () => {
getMercureInfo.mockResolvedValue(mercureInfo);
const getState = createGetStateMock(true);
const settings = createSettings(true);
await loadMercureInfo()(dispatch, getState, {});
await loadMercureInfo(settings)(dispatch, vi.fn(), {});
expect(getMercureInfo).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledTimes(2);

View file

@ -4,6 +4,8 @@ import { MemoryRouter } from 'react-router-dom';
import type { MercureInfo } from '../../src/mercure/reducers/mercureInfo';
import { Overview as overviewCreator } from '../../src/overview/Overview';
import { prettify } from '../../src/utils/helpers/numbers';
import { RoutesPrefixProvider } from '../../src/utils/routesPrefix';
import { SettingsProvider } from '../../src/utils/settings';
import { renderWithEvents } from '../__helpers__/setUpTest';
describe('<Overview />', () => {
@ -16,26 +18,28 @@ describe('<Overview />', () => {
const shortUrls = {
pagination: { totalItems: 83710 },
};
const serverId = '123';
const routesPrefix = '/server/123';
const setUp = (loading = false, excludeBots = false) => renderWithEvents(
<MemoryRouter>
<Overview
listShortUrls={listShortUrls}
listTags={listTags}
loadVisitsOverview={loadVisitsOverview}
shortUrlsList={fromPartial({ loading, shortUrls })}
tagsList={fromPartial({ loading, tags: ['foo', 'bar', 'baz'] })}
visitsOverview={fromPartial({
loading,
nonOrphanVisits: { total: 3456, bots: 1000, nonBots: 2456 },
orphanVisits: { total: 28, bots: 15, nonBots: 13 },
})}
selectedServer={fromPartial({ id: serverId })}
createNewVisits={vi.fn()}
loadMercureInfo={vi.fn()}
mercureInfo={fromPartial<MercureInfo>({})}
settings={fromPartial({ visits: { excludeBots } })}
/>
<SettingsProvider value={fromPartial({ visits: { excludeBots } })}>
<RoutesPrefixProvider value={routesPrefix}>
<Overview
listShortUrls={listShortUrls}
listTags={listTags}
loadVisitsOverview={loadVisitsOverview}
shortUrlsList={fromPartial({ loading, shortUrls })}
tagsList={fromPartial({ loading, tags: ['foo', 'bar', 'baz'] })}
visitsOverview={fromPartial({
loading,
nonOrphanVisits: { total: 3456, bots: 1000, nonBots: 2456 },
orphanVisits: { total: 28, bots: 15, nonBots: 13 },
})}
createNewVisits={vi.fn()}
loadMercureInfo={vi.fn()}
mercureInfo={fromPartial<MercureInfo>({})}
/>
</RoutesPrefixProvider>
</SettingsProvider>
</MemoryRouter>,
);
@ -75,12 +79,13 @@ describe('<Overview />', () => {
const links = screen.getAllByRole('link');
expect(links).toHaveLength(5);
expect(links[0]).toHaveAttribute('href', `/server/${serverId}/orphan-visits`);
expect(links[1]).toHaveAttribute('href', `/server/${serverId}/list-short-urls/1`);
expect(links[2]).toHaveAttribute('href', `/server/${serverId}/manage-tags`);
expect(links[3]).toHaveAttribute('href', `/server/${serverId}/create-short-url`);
expect(links[4]).toHaveAttribute('href', `/server/${serverId}/list-short-urls/1`);
expect(links).toHaveLength(6);
expect(links[0]).toHaveAttribute('href', `${routesPrefix}/non-orphan-visits`);
expect(links[1]).toHaveAttribute('href', `${routesPrefix}/orphan-visits`);
expect(links[2]).toHaveAttribute('href', `${routesPrefix}/list-short-urls/1`);
expect(links[3]).toHaveAttribute('href', `${routesPrefix}/manage-tags`);
expect(links[4]).toHaveAttribute('href', `${routesPrefix}/create-short-url`);
expect(links[5]).toHaveAttribute('href', `${routesPrefix}/list-short-urls/1`);
});
it.each([

View file

@ -1,27 +1,17 @@
import { screen, waitFor } from '@testing-library/react';
import type { ReactNode } from 'react';
import type { PropsWithChildren } from 'react';
import { MemoryRouter } from 'react-router-dom';
import type { HighlightCardProps } from '../../../src/overview/helpers/HighlightCard';
import { HighlightCard } from '../../../src/overview/helpers/HighlightCard';
import { renderWithEvents } from '../../__helpers__/setUpTest';
describe('<HighlightCard />', () => {
const setUp = (props: HighlightCardProps & { children?: ReactNode }) => renderWithEvents(
const setUp = (props: PropsWithChildren<Partial<HighlightCardProps>>) => renderWithEvents(
<MemoryRouter>
<HighlightCard {...props} />
<HighlightCard link="" title="" {...props} />
</MemoryRouter>,
);
it.each([
[undefined],
[''],
])('does not render icon when there is no link', (link) => {
setUp({ title: 'foo', link });
expect(screen.queryByRole('img', { hidden: true })).not.toBeInTheDocument();
expect(screen.queryByRole('link')).not.toBeInTheDocument();
});
it.each([
['foo'],
['bar'],

View file

@ -1,18 +1,21 @@
import { screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import type { VisitsHighlightCardProps } from '../../../src/overview/helpers/VisitsHighlightCard';
import { VisitsHighlightCard } from '../../../src/overview/helpers/VisitsHighlightCard';
import { renderWithEvents } from '../../__helpers__/setUpTest';
describe('<VisitsHighlightCard />', () => {
const setUp = (props: Partial<VisitsHighlightCardProps> = {}) => renderWithEvents(
<VisitsHighlightCard
loading={false}
visitsSummary={{ total: 0 }}
excludeBots={false}
title=""
link=""
{...props}
/>,
<MemoryRouter>
<VisitsHighlightCard
loading={false}
visitsSummary={{ total: 0 }}
excludeBots={false}
title=""
link=""
{...props}
/>
</MemoryRouter>,
);
it.each([

View file

@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { CreateShortUrl as createShortUrlsCreator } from '../../src/short-urls/CreateShortUrl';
import type { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation';
import { SettingsProvider } from '../../src/utils/settings';
describe('<CreateShortUrl />', () => {
const ShortUrlForm = () => <span>ShortUrlForm</span>;
@ -11,13 +12,13 @@ describe('<CreateShortUrl />', () => {
const createShortUrl = vi.fn(async () => Promise.resolve());
const CreateShortUrl = createShortUrlsCreator(ShortUrlForm, CreateShortUrlResult);
const setUp = () => render(
<CreateShortUrl
shortUrlCreation={shortUrlCreationResult}
createShortUrl={createShortUrl}
selectedServer={null}
resetCreateShortUrl={() => {}}
settings={fromPartial({ shortUrlCreation })}
/>,
<SettingsProvider value={fromPartial({ shortUrlCreation })}>
<CreateShortUrl
shortUrlCreation={shortUrlCreationResult}
createShortUrl={createShortUrl}
resetCreateShortUrl={() => {}}
/>
</SettingsProvider>,
);
it('renders computed initial state', () => {

View file

@ -4,20 +4,21 @@ import { MemoryRouter } from 'react-router-dom';
import { EditShortUrl as createEditShortUrl } from '../../src/short-urls/EditShortUrl';
import type { ShortUrlDetail } from '../../src/short-urls/reducers/shortUrlDetail';
import type { ShortUrlEdition } from '../../src/short-urls/reducers/shortUrlEdition';
import { SettingsProvider } from '../../src/utils/settings';
describe('<EditShortUrl />', () => {
const shortUrlCreation = { validateUrls: true };
const EditShortUrl = createEditShortUrl(() => <span>ShortUrlForm</span>);
const setUp = (detail: Partial<ShortUrlDetail> = {}, edition: Partial<ShortUrlEdition> = {}) => render(
<MemoryRouter>
<EditShortUrl
settings={fromPartial({ shortUrlCreation })}
selectedServer={null}
shortUrlDetail={fromPartial(detail)}
shortUrlEdition={fromPartial(edition)}
getShortUrlDetail={vi.fn()}
editShortUrl={vi.fn(async () => Promise.resolve())}
/>
<SettingsProvider value={fromPartial({ shortUrlCreation })}>
<EditShortUrl
shortUrlDetail={fromPartial(detail)}
shortUrlEdition={fromPartial(edition)}
getShortUrlDetail={vi.fn()}
editShortUrl={vi.fn(async () => Promise.resolve())}
/>
</SettingsProvider>
</MemoryRouter>,
);

View file

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { ShlinkPaginator } from '../../src/api/types';
import type { ShlinkPaginator } from '../../src/api-contract';
import { Paginator } from '../../src/short-urls/Paginator';
import { ELLIPSIS } from '../../src/utils/helpers/pagination';
@ -9,7 +9,7 @@ describe('<Paginator />', () => {
const buildPaginator = (pagesCount?: number) => fromPartial<ShlinkPaginator>({ pagesCount, currentPage: 1 });
const setUp = (paginator?: ShlinkPaginator, currentQueryString?: string) => render(
<MemoryRouter>
<Paginator serverId="abc123" paginator={paginator} currentQueryString={currentQueryString} />
<Paginator paginator={paginator} currentQueryString={currentQueryString} />
</MemoryRouter>,
);

View file

@ -2,25 +2,26 @@ import { screen } from '@testing-library/react';
import type { UserEvent } from '@testing-library/user-event/setup/setup';
import { fromPartial } from '@total-typescript/shoehorn';
import { formatISO } from 'date-fns';
import type { ReachableServer, SelectedServer } from '../../../src/servers/data';
import type { OptionalString } from '../../../src/utils/utils';
import type { Mode } from '../../src/short-urls/ShortUrlForm';
import { ShortUrlForm as createShortUrlForm } from '../../src/short-urls/ShortUrlForm';
import { parseDate } from '../../src/utils/dates/helpers/date';
import { FeaturesProvider } from '../../src/utils/features';
import { renderWithEvents } from '../__helpers__/setUpTest';
describe('<ShortUrlForm />', () => {
const createShortUrl = vi.fn(async () => Promise.resolve());
const ShortUrlForm = createShortUrlForm(() => <span>TagsSelector</span>, () => <span>DomainSelector</span>);
const setUp = (selectedServer: SelectedServer = null, mode: Mode = 'create', title?: OptionalString) =>
const setUp = (withDeviceLongUrls = false, mode: Mode = 'create', title?: OptionalString) =>
renderWithEvents(
<ShortUrlForm
selectedServer={selectedServer}
mode={mode}
saving={false}
initialState={{ validateUrl: true, findIfExists: false, title, longUrl: '' }}
onSave={createShortUrl}
/>,
<FeaturesProvider value={fromPartial({ deviceLongUrls: withDeviceLongUrls })}>
<ShortUrlForm
mode={mode}
saving={false}
initialState={{ validateUrl: true, findIfExists: false, title, longUrl: '' }}
onSave={createShortUrl}
/>
</FeaturesProvider>,
);
it.each([
@ -29,14 +30,14 @@ describe('<ShortUrlForm />', () => {
await user.type(screen.getByPlaceholderText('Custom slug'), 'my-slug');
},
{ customSlug: 'my-slug' },
null,
false,
],
[
async (user: UserEvent) => {
await user.type(screen.getByPlaceholderText('Short code length'), '15');
},
{ shortCodeLength: '15' },
null,
false,
],
[
async (user: UserEvent) => {
@ -49,10 +50,10 @@ describe('<ShortUrlForm />', () => {
ios: 'https://ios.com',
},
},
fromPartial<ReachableServer>({ version: '3.5.0' }),
true,
],
])('saves short URL with data set in form controls', async (extraFields, extraExpectedValues, selectedServer) => {
const { user } = setUp(selectedServer);
])('saves short URL with data set in form controls', async (extraFields, extraExpectedValues, withDeviceLongUrls) => {
const { user } = setUp(withDeviceLongUrls);
const validSince = parseDate('2017-01-01', 'yyyy-MM-dd');
const validUntil = parseDate('2017-01-06', 'yyyy-MM-dd');
@ -83,7 +84,7 @@ describe('<ShortUrlForm />', () => {
])(
'renders expected amount of cards based on server capabilities and mode',
(mode, expectedAmountOfCards) => {
setUp(null, mode);
setUp(false, mode);
const cards = screen.queryAllByRole('heading');
expect(cards).toHaveLength(expectedAmountOfCards);
@ -100,7 +101,7 @@ describe('<ShortUrlForm />', () => {
[undefined, false, undefined],
['old title', false, null],
])('sends expected title based on original and new values', async (originalTitle, withNewTitle, expectedSentTitle) => {
const { user } = setUp(fromPartial({ version: '2.6.0' }), 'create', originalTitle);
const { user } = setUp(false, 'create', originalTitle);
await user.type(screen.getByPlaceholderText('URL to be shortened'), 'https://long-domain.com/foo/bar');
await user.clear(screen.getByPlaceholderText('Title'));
@ -114,19 +115,10 @@ describe('<ShortUrlForm />', () => {
}));
});
it.each([
[fromPartial<ReachableServer>({ version: '3.0.0' }), false],
[fromPartial<ReachableServer>({ version: '3.4.0' }), false],
[fromPartial<ReachableServer>({ version: '3.5.0' }), true],
[fromPartial<ReachableServer>({ version: '3.6.0' }), true],
])('shows device-specific long URLs only for servers supporting it', (selectedServer, fieldsExist) => {
setUp(selectedServer);
const placeholders = ['Android-specific redirection', 'iOS-specific redirection', 'Desktop-specific redirection'];
it('shows device-specific long URLs only when supported', () => {
setUp(true);
if (fieldsExist) {
placeholders.forEach((placeholder) => expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument());
} else {
placeholders.forEach((placeholder) => expect(screen.queryByPlaceholderText(placeholder)).not.toBeInTheDocument());
}
const placeholders = ['Android-specific redirection', 'iOS-specific redirection', 'Desktop-specific redirection'];
placeholders.forEach((placeholder) => expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument());
});
});

View file

@ -2,10 +2,12 @@ import { screen, waitFor } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { endOfDay, formatISO, startOfDay } from 'date-fns';
import { MemoryRouter, useLocation, useNavigate } from 'react-router-dom';
import type { ReachableServer, SelectedServer } from '../../../src/servers/data';
import { ShortUrlsFilteringBar as filteringBarCreator } from '../../src/short-urls/ShortUrlsFilteringBar';
import { formatIsoDate } from '../../src/utils/dates/helpers/date';
import type { DateRange } from '../../src/utils/dates/helpers/dateIntervals';
import { FeaturesProvider } from '../../src/utils/features';
import { RoutesPrefixProvider } from '../../src/utils/routesPrefix';
import { SettingsProvider } from '../../src/utils/settings';
import { renderWithEvents } from '../__helpers__/setUpTest';
vi.mock('react-router-dom', async () => ({
@ -20,18 +22,19 @@ describe('<ShortUrlsFilteringBar />', () => {
const navigate = vi.fn();
const handleOrderBy = vi.fn();
const now = new Date();
const setUp = (search = '', selectedServer?: SelectedServer) => {
const setUp = (search = '', filterDisabledUrls = true) => {
(useLocation as any).mockReturnValue({ search });
(useNavigate as any).mockReturnValue(navigate);
return renderWithEvents(
<MemoryRouter>
<ShortUrlsFilteringBar
selectedServer={selectedServer ?? fromPartial({})}
order={{}}
handleOrderBy={handleOrderBy}
settings={fromPartial({ visits: {} })}
/>
<SettingsProvider value={fromPartial({ visits: {} })}>
<FeaturesProvider value={fromPartial({ filterDisabledUrls })}>
<RoutesPrefixProvider value="/server/1">
<ShortUrlsFilteringBar order={{}} handleOrderBy={handleOrderBy} />
</RoutesPrefixProvider>
</FeaturesProvider>
</SettingsProvider>
</MemoryRouter>,
);
};
@ -71,16 +74,14 @@ describe('<ShortUrlsFilteringBar />', () => {
});
it.each([
['tags=foo,bar,baz', fromPartial<ReachableServer>({ version: '3.0.0' }), true],
['tags=foo,bar', fromPartial<ReachableServer>({ version: '3.1.0' }), true],
['tags=foo', fromPartial<ReachableServer>({ version: '3.0.0' }), false],
['', fromPartial<ReachableServer>({ version: '3.0.0' }), false],
['tags=foo,bar,baz', fromPartial<ReachableServer>({ version: '2.10.0' }), false],
['', fromPartial<ReachableServer>({ version: '2.10.0' }), false],
{ search: 'tags=foo,bar,baz', shouldHaveComponent: true },
{ search: 'tags=foo,bar', shouldHaveComponent: true },
{ search: 'tags=foo', shouldHaveComponent: false },
{ search: '', shouldHaveComponent: false },
])(
'renders tags mode toggle if the server supports it and there is more than one tag selected',
(search, selectedServer, shouldHaveComponent) => {
setUp(search, selectedServer);
'renders tags mode toggle if there is more than one tag selected',
({ search, shouldHaveComponent }) => {
setUp(search);
if (shouldHaveComponent) {
expect(screen.getByLabelText('Change tags mode')).toBeInTheDocument();
@ -95,7 +96,7 @@ describe('<ShortUrlsFilteringBar />', () => {
['&tagsMode=all', 'With all the tags.'],
['&tagsMode=any', 'With any of the tags.'],
])('expected tags mode tooltip title', async (initialTagsMode, expectedToggleText) => {
const { user } = setUp(`tags=foo,bar${initialTagsMode}`, fromPartial({ version: '3.0.0' }));
const { user } = setUp(`tags=foo,bar${initialTagsMode}`, true);
await user.hover(screen.getByLabelText('Change tags mode'));
expect(await screen.findByRole('tooltip')).toHaveTextContent(expectedToggleText);
@ -106,7 +107,7 @@ describe('<ShortUrlsFilteringBar />', () => {
['&tagsMode=all', 'tagsMode=any'],
['&tagsMode=any', 'tagsMode=all'],
])('redirects to first page when tags mode changes', async (initialTagsMode, expectedRedirectTagsMode) => {
const { user } = setUp(`tags=foo,bar${initialTagsMode}`, fromPartial({ version: '3.0.0' }));
const { user } = setUp(`tags=foo,bar${initialTagsMode}`, true);
expect(navigate).not.toHaveBeenCalled();
await user.click(screen.getByLabelText('Change tags mode'));
@ -124,7 +125,7 @@ describe('<ShortUrlsFilteringBar />', () => {
['excludePastValidUntil=false', /Exclude enabled in the past/, 'excludePastValidUntil=true'],
['excludePastValidUntil=true', /Exclude enabled in the past/, 'excludePastValidUntil=false'],
])('allows to toggle filters through filtering dropdown', async (search, menuItemName, expectedQuery) => {
const { user } = setUp(search, fromPartial({ version: '3.4.0' }));
const { user } = setUp(search, true);
const toggleFilter = async (name: RegExp) => {
await user.click(screen.getByRole('button', { name: 'Filters' }));
await waitFor(() => screen.findByRole('menu'));

View file

@ -1,13 +1,14 @@
import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter, useNavigate } from 'react-router-dom';
import type { SemVer } from '../../../src/utils/helpers/version';
import type { Settings } from '../../src';
import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
import type { ShortUrlsOrder } from '../../src/short-urls/data';
import type { ShortUrlsList as ShortUrlsListModel } from '../../src/short-urls/reducers/shortUrlsList';
import { ShortUrlsList as createShortUrlsList } from '../../src/short-urls/ShortUrlsList';
import type { ShortUrlsTableType } from '../../src/short-urls/ShortUrlsTable';
import { FeaturesProvider } from '../../src/utils/features';
import { SettingsProvider } from '../../src/utils/settings';
import { renderWithEvents } from '../__helpers__/setUpTest';
vi.mock('react-router-dom', async () => ({
@ -35,15 +36,17 @@ describe('<ShortUrlsList />', () => {
},
});
const ShortUrlsList = createShortUrlsList(ShortUrlsTable, ShortUrlsFilteringBar);
const setUp = (settings: Partial<Settings> = {}, version: SemVer = '3.0.0') => renderWithEvents(
const setUp = (settings: Partial<Settings> = {}, excludeBotsOnShortUrls = true) => renderWithEvents(
<MemoryRouter>
<ShortUrlsList
{...fromPartial<MercureBoundProps>({ mercureInfo: { loading: true } })}
listShortUrls={listShortUrlsMock}
shortUrlsList={shortUrlsList}
selectedServer={fromPartial({ id: '1', version })}
settings={fromPartial(settings)}
/>
<SettingsProvider value={fromPartial(settings)}>
<FeaturesProvider value={fromPartial({ excludeBotsOnShortUrls })}>
<ShortUrlsList
{...fromPartial<MercureBoundProps>({ mercureInfo: { loading: true } })}
listShortUrls={listShortUrlsMock}
shortUrlsList={shortUrlsList}
/>
</FeaturesProvider>
</SettingsProvider>
</MemoryRouter>,
);
@ -93,26 +96,26 @@ describe('<ShortUrlsList />', () => {
shortUrlsList: {
defaultOrdering: { field: 'visits', dir: 'ASC' },
},
}), '3.3.0' as SemVer, { field: 'visits', dir: 'ASC' }],
}), false, { field: 'visits', dir: 'ASC' }],
[fromPartial<Settings>({
shortUrlsList: {
defaultOrdering: { field: 'visits', dir: 'ASC' },
},
visits: { excludeBots: true },
}), '3.3.0' as SemVer, { field: 'visits', dir: 'ASC' }],
}), false, { field: 'visits', dir: 'ASC' }],
[fromPartial<Settings>({
shortUrlsList: {
defaultOrdering: { field: 'visits', dir: 'ASC' },
},
}), '3.4.0' as SemVer, { field: 'visits', dir: 'ASC' }],
}), true, { field: 'visits', dir: 'ASC' }],
[fromPartial<Settings>({
shortUrlsList: {
defaultOrdering: { field: 'visits', dir: 'ASC' },
},
visits: { excludeBots: true },
}), '3.4.0' as SemVer, { field: 'nonBotVisits', dir: 'ASC' }],
])('parses order by based on server version and config', (settings, serverVersion, expectedOrderBy) => {
setUp(settings, serverVersion);
}), true, { field: 'nonBotVisits', dir: 'ASC' }],
])('parses order by based on supported features version and config', (settings, excludeBotsOnShortUrls, expectedOrderBy) => {
setUp(settings, excludeBotsOnShortUrls);
expect(listShortUrlsMock).toHaveBeenCalledWith(expect.objectContaining({ orderBy: expectedOrderBy }));
});
});

View file

@ -1,6 +1,5 @@
import { fireEvent, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import type { SelectedServer } from '../../../src/servers/data';
import type { ShortUrlsOrderableFields } from '../../src/short-urls/data';
import { SHORT_URLS_ORDERABLE_FIELDS } from '../../src/short-urls/data';
import type { ShortUrlsList } from '../../src/short-urls/reducers/shortUrlsList';
@ -11,8 +10,8 @@ describe('<ShortUrlsTable />', () => {
const shortUrlsList = fromPartial<ShortUrlsList>({});
const orderByColumn = vi.fn();
const ShortUrlsTable = shortUrlsTableCreator(() => <span>ShortUrlsRow</span>);
const setUp = (server: SelectedServer = null) => renderWithEvents(
<ShortUrlsTable shortUrlsList={shortUrlsList} selectedServer={server} orderByColumn={() => orderByColumn} />,
const setUp = () => renderWithEvents(
<ShortUrlsTable shortUrlsList={shortUrlsList} orderByColumn={() => orderByColumn} />,
);
it('should render inner table by default', () => {
@ -54,7 +53,7 @@ describe('<ShortUrlsTable />', () => {
});
it('should render composed title column', () => {
setUp(fromPartial({ version: '2.0.0' }));
setUp();
const { innerHTML } = screen.getAllByRole('columnheader')[2];

View file

@ -1,7 +1,6 @@
import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { NotFoundServer, SelectedServer } from '../../../../src/servers/data';
import type { ShortUrl } from '../../../src/short-urls/data';
import { ExportShortUrlsBtn as createExportShortUrlsBtn } from '../../../src/short-urls/helpers/ExportShortUrlsBtn';
import type { ReportExporter } from '../../../src/utils/services/ReportExporter';
@ -13,9 +12,9 @@ describe('<ExportShortUrlsBtn />', () => {
const exportShortUrls = vi.fn();
const reportExporter = fromPartial<ReportExporter>({ exportShortUrls });
const ExportShortUrlsBtn = createExportShortUrlsBtn(buildShlinkApiClient, reportExporter);
const setUp = (amount?: number, selectedServer?: SelectedServer) => renderWithEvents(
const setUp = (amount?: number) => renderWithEvents(
<MemoryRouter>
<ExportShortUrlsBtn selectedServer={selectedServer ?? fromPartial({})} amount={amount} />
<ExportShortUrlsBtn amount={amount} />
</MemoryRouter>,
);
@ -28,17 +27,6 @@ describe('<ExportShortUrlsBtn />', () => {
expect(screen.getByText(/Export/)).toHaveTextContent(`Export (${expectedAmount})`);
});
it.each([
[null],
[fromPartial<NotFoundServer>({})],
])('does nothing on click if selected server is not reachable', async (selectedServer) => {
const { user } = setUp(0, selectedServer);
await user.click(screen.getByRole('button'));
expect(listShortUrls).not.toHaveBeenCalled();
expect(exportShortUrls).not.toHaveBeenCalled();
});
it.each([
[10, 1],
[30, 2],
@ -48,7 +36,7 @@ describe('<ExportShortUrlsBtn />', () => {
[385, 20],
])('loads proper amount of pages based on the amount of results', async (amount, expectedPageLoads) => {
listShortUrls.mockResolvedValue({ data: [] });
const { user } = setUp(amount, fromPartial({ id: '123' }));
const { user } = setUp(amount);
await user.click(screen.getByRole('button'));
@ -63,7 +51,7 @@ describe('<ExportShortUrlsBtn />', () => {
tags: [],
})],
});
const { user } = setUp(undefined, fromPartial({ id: '123' }));
const { user } = setUp();
await user.click(screen.getByRole('button'));

View file

@ -1,6 +1,5 @@
import { fireEvent, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import type { SemVer } from '../../../../src/utils/helpers/version';
import { QrCodeModal as createQrCodeModal } from '../../../src/short-urls/helpers/QrCodeModal';
import { renderWithEvents } from '../../__helpers__/setUpTest';
@ -8,11 +7,10 @@ describe('<QrCodeModal />', () => {
const saveImage = vi.fn().mockReturnValue(Promise.resolve());
const QrCodeModal = createQrCodeModal(fromPartial({ saveImage }));
const shortUrl = 'https://s.test/abc123';
const setUp = (version: SemVer = '2.8.0') => renderWithEvents(
const setUp = () => renderWithEvents(
<QrCodeModal
isOpen
shortUrl={fromPartial({ shortUrl })}
selectedServer={fromPartial({ version })}
toggle={() => {}}
/>,
);
@ -63,16 +61,14 @@ describe('<QrCodeModal />', () => {
});
it('shows expected components based on server version', () => {
const { container } = setUp();
setUp();
const dropdowns = screen.getAllByRole('button');
const firstCol = container.parentNode?.querySelectorAll('.d-grid').item(0);
expect(dropdowns).toHaveLength(2 + 1); // Add one because of the close button
expect(firstCol).toHaveClass('col-md-4');
expect(dropdowns).toHaveLength(2 + 2); // Add two because of the close and download buttons
});
it('saves the QR code image when clicking the Download button', async () => {
const { user } = setUp('2.9.0');
const { user } = setUp();
expect(saveImage).not.toHaveBeenCalled();
await user.click(screen.getByRole('button', { name: /^Download/ }));

View file

@ -1,23 +1,22 @@
import { render, screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { NotFoundServer, ReachableServer } from '../../../../src/servers/data';
import type { ShortUrl } from '../../../src/short-urls/data';
import type { LinkSuffix } from '../../../src/short-urls/helpers/ShortUrlDetailLink';
import { ShortUrlDetailLink } from '../../../src/short-urls/helpers/ShortUrlDetailLink';
import { RoutesPrefixProvider } from '../../../src/utils/routesPrefix';
describe('<ShortUrlDetailLink />', () => {
it.each([
[undefined, undefined],
[null, null],
[fromPartial<ReachableServer>({ id: '1' }), null],
[fromPartial<ReachableServer>({ id: '1' }), undefined],
[fromPartial<NotFoundServer>({}), fromPartial<ShortUrl>({})],
[null, fromPartial<ShortUrl>({})],
[undefined, fromPartial<ShortUrl>({})],
])('only renders a plain span when either server or short URL are not set', (selectedServer, shortUrl) => {
[false, undefined],
[false, null],
[true, null],
[true, undefined],
[false, fromPartial<ShortUrl>({})],
[false, fromPartial<ShortUrl>({})],
])('only renders a plain span when either server or short URL are not set', (asLink, shortUrl) => {
render(
<ShortUrlDetailLink selectedServer={selectedServer} shortUrl={shortUrl} suffix="visits">
<ShortUrlDetailLink shortUrl={shortUrl} asLink={asLink} suffix="visits">
Something
</ShortUrlDetailLink>,
);
@ -28,35 +27,37 @@ describe('<ShortUrlDetailLink />', () => {
it.each([
[
fromPartial<ReachableServer>({ id: '1' }),
'/server/1',
fromPartial<ShortUrl>({ shortCode: 'abc123' }),
'visits' as LinkSuffix,
'/server/1/short-code/abc123/visits',
],
[
fromPartial<ReachableServer>({ id: '3' }),
'/foobar',
fromPartial<ShortUrl>({ shortCode: 'def456', domain: 'example.com' }),
'visits' as LinkSuffix,
'/server/3/short-code/def456/visits?domain=example.com',
'/foobar/short-code/def456/visits?domain=example.com',
],
[
fromPartial<ReachableServer>({ id: '1' }),
'/server/1',
fromPartial<ShortUrl>({ shortCode: 'abc123' }),
'edit' as LinkSuffix,
'/server/1/short-code/abc123/edit',
],
[
fromPartial<ReachableServer>({ id: '3' }),
'/server/3',
fromPartial<ShortUrl>({ shortCode: 'def456', domain: 'example.com' }),
'edit' as LinkSuffix,
'/server/3/short-code/def456/edit?domain=example.com',
],
])('renders link with expected query when', (selectedServer, shortUrl, suffix, expectedLink) => {
])('renders link with expected query when', (routesPrefix, shortUrl, suffix, expectedLink) => {
render(
<MemoryRouter>
<ShortUrlDetailLink selectedServer={selectedServer} shortUrl={shortUrl} suffix={suffix}>
Something
</ShortUrlDetailLink>
<RoutesPrefixProvider value={routesPrefix}>
<ShortUrlDetailLink shortUrl={shortUrl} suffix={suffix} asLink>
Something
</ShortUrlDetailLink>
</RoutesPrefixProvider>
</MemoryRouter>,
);
expect(screen.getByRole('link')).toHaveProperty('href', expect.stringContaining(expectedLink));

View file

@ -1,7 +1,7 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkVisitsSummary } from '../../../src/api/types';
import type { ShlinkVisitsSummary } from '../../../src/api-contract';
import type { ShortUrl, ShortUrlMeta } from '../../../src/short-urls/data';
import { ShortUrlStatus } from '../../../src/short-urls/helpers/ShortUrlStatus';

View file

@ -3,18 +3,17 @@ import { fromPartial } from '@total-typescript/shoehorn';
import { addDays, formatISO, subDays } from 'date-fns';
import { last } from 'ramda';
import { MemoryRouter, useLocation } from 'react-router-dom';
import type { ReachableServer } from '../../../../src/servers/data';
import type { TimeoutToggle } from '../../../../src/utils/helpers/hooks';
import type { OptionalString } from '../../../../src/utils/utils';
import type { Settings } from '../../../src';
import type { ShortUrl, ShortUrlMeta } from '../../../src/short-urls/data';
import { ShortUrlsRow as createShortUrlsRow } from '../../../src/short-urls/helpers/ShortUrlsRow';
import { now, parseDate } from '../../../src/utils/dates/helpers/date';
import type { TimeoutToggle } from '../../../src/utils/helpers/hooks';
import { SettingsProvider } from '../../../src/utils/settings';
import { renderWithEvents } from '../../__helpers__/setUpTest';
import { colorGeneratorMock } from '../../utils/services/__mocks__/ColorGenerator.mock';
interface SetUpOptions {
title?: OptionalString;
title?: string | null;
tags?: string[];
meta?: ShortUrlMeta;
settings?: Partial<Settings>;
@ -28,7 +27,6 @@ vi.mock('react-router-dom', async () => ({
describe('<ShortUrlsRow />', () => {
const timeoutToggle = vi.fn(() => true);
const useTimeoutToggle = vi.fn(() => [false, timeoutToggle]) as TimeoutToggle;
const server = fromPartial<ReachableServer>({ url: 'https://s.test' });
const shortUrl: ShortUrl = {
shortCode: 'abc123',
shortUrl: 'https://s.test/abc123',
@ -54,16 +52,16 @@ describe('<ShortUrlsRow />', () => {
(useLocation as any).mockReturnValue({ search });
return renderWithEvents(
<MemoryRouter>
<table>
<tbody>
<ShortUrlsRow
selectedServer={server}
shortUrl={{ ...shortUrl, title, tags, meta: { ...shortUrl.meta, ...meta } }}
onTagClick={() => null}
settings={fromPartial(settings)}
/>
</tbody>
</table>
<SettingsProvider value={fromPartial(settings)}>
<table>
<tbody>
<ShortUrlsRow
shortUrl={{ ...shortUrl, title, tags, meta: { ...shortUrl.meta, ...meta } }}
onTagClick={() => null}
/>
</tbody>
</table>
</SettingsProvider>
</MemoryRouter>,
);
};

View file

@ -1,21 +1,19 @@
import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import type { ReachableServer } from '../../../../src/servers/data';
import type { ShortUrl } from '../../../src/short-urls/data';
import { ShortUrlsRowMenu as createShortUrlsRowMenu } from '../../../src/short-urls/helpers/ShortUrlsRowMenu';
import { renderWithEvents } from '../../__helpers__/setUpTest';
describe('<ShortUrlsRowMenu />', () => {
const ShortUrlsRowMenu = createShortUrlsRowMenu(() => <i>DeleteShortUrlModal</i>, () => <i>QrCodeModal</i>);
const selectedServer = fromPartial<ReachableServer>({ id: 'abc123' });
const shortUrl = fromPartial<ShortUrl>({
shortCode: 'abc123',
shortUrl: 'https://s.test/abc123',
});
const setUp = () => renderWithEvents(
<MemoryRouter>
<ShortUrlsRowMenu selectedServer={selectedServer} shortUrl={shortUrl} />
<ShortUrlsRowMenu shortUrl={shortUrl} />
</MemoryRouter>,
);

View file

@ -1,6 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import type { ShlinkApiClient } from '../../../src/api-contract';
import type { ShortUrl } from '../../../src/short-urls/data';
import {
createShortUrl as createShortUrlCreator,
@ -51,11 +50,10 @@ describe('shortUrlCreationReducer', () => {
describe('createShortUrl', () => {
const dispatch = vi.fn();
const getState = () => fromPartial<ShlinkState>({});
it('calls API on success', async () => {
createShortUrlCall.mockResolvedValue(shortUrl);
await createShortUrl({ longUrl: 'foo' })(dispatch, getState, {});
await createShortUrl({ longUrl: 'foo' })(dispatch, vi.fn(), {});
expect(createShortUrlCall).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledTimes(2);

View file

@ -1,6 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ProblemDetailsError } from '../../../src/api/types/errors';
import type { ProblemDetailsError } from '../../../src/api-contract';
import {
deleteShortUrl as deleteShortUrlCreator,
shortUrlDeletionReducerCreator,

View file

@ -1,6 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import type { ShlinkApiClient } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store';
import type { ShortUrl } from '../../../src/short-urls/data';
import { shortUrlDetailReducerCreator } from '../../../src/short-urls/reducers/shortUrlDetail';
import type { ShortUrlsList } from '../../../src/short-urls/reducers/shortUrlsList';
@ -40,7 +40,7 @@ describe('shortUrlDetailReducer', () => {
describe('getShortUrlDetail', () => {
const dispatchMock = vi.fn();
const buildGetState = (shortUrlsList?: ShortUrlsList) => () => fromPartial<ShlinkState>({ shortUrlsList });
const buildGetState = (shortUrlsList?: ShortUrlsList) => () => fromPartial<RootState>({ shortUrlsList });
it.each([
[undefined],

View file

@ -1,6 +1,4 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkState } from '../../../../src/container/types';
import type { SelectedServer } from '../../../../src/servers/data';
import type { ShortUrl } from '../../../src/short-urls/data';
import {
editShortUrl as editShortUrlCreator,
@ -45,12 +43,9 @@ describe('shortUrlEditionReducer', () => {
describe('editShortUrl', () => {
const dispatch = vi.fn();
const createGetState = (selectedServer: SelectedServer = null) => () => fromPartial<ShlinkState>({
selectedServer,
});
it.each([[undefined], [null], ['example.com']])('dispatches short URL on success', async (domain) => {
await editShortUrl({ shortCode, domain, data: { longUrl } })(dispatch, createGetState(), {});
await editShortUrl({ shortCode, domain, data: { longUrl } })(dispatch, vi.fn(), {});
expect(buildShlinkApiClient).toHaveBeenCalledTimes(1);
expect(updateShortUrl).toHaveBeenCalledTimes(1);

View file

@ -1,6 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkShortUrlsResponse } from '../../../src/api/types';
import type { ShlinkApiClient, ShlinkShortUrlsResponse } from '../../../src/api-contract';
import type { ShortUrl } from '../../../src/short-urls/data';
import { createShortUrl as createShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlCreation';
import { shortUrlDeleted } from '../../../src/short-urls/reducers/shortUrlDeletion';
@ -187,7 +186,7 @@ describe('shortUrlsListReducer', () => {
describe('listShortUrls', () => {
const dispatch = vi.fn();
const getState = vi.fn().mockReturnValue({ selectedServer: {} });
const getState = vi.fn();
it('dispatches proper actions if API client request succeeds', async () => {
listShortUrlsMock.mockResolvedValue({});

View file

@ -5,20 +5,22 @@ import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercure
import type { TagsList } from '../../src/tags/reducers/tagsList';
import type { TagsListProps } from '../../src/tags/TagsList';
import { TagsList as createTagsList } from '../../src/tags/TagsList';
import { SettingsProvider } from '../../src/utils/settings';
import { renderWithEvents } from '../__helpers__/setUpTest';
describe('<TagsList />', () => {
const filterTags = vi.fn();
const TagsListComp = createTagsList(({ sortedTags }) => <>TagsTable ({sortedTags.map((t) => t.visits).join(',')})</>);
const setUp = (tagsList: Partial<TagsList>, excludeBots = false) => renderWithEvents(
<TagsListComp
{...fromPartial<TagsListProps>({})}
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
forceListTags={identity}
filterTags={filterTags}
tagsList={fromPartial(tagsList)}
settings={fromPartial({ visits: { excludeBots } })}
/>,
<SettingsProvider value={fromPartial({ visits: { excludeBots } })}>
<TagsListComp
{...fromPartial<TagsListProps>({})}
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
forceListTags={identity}
filterTags={filterTags}
tagsList={fromPartial(tagsList)}
/>
</SettingsProvider>,
);
it('shows a loading message when tags are being loaded', () => {

View file

@ -19,7 +19,6 @@ describe('<TagsTable />', () => {
return renderWithEvents(
<TagsTable
sortedTags={sortedTags.map((tag) => fromPartial({ tag }))}
selectedServer={fromPartial({})}
currentOrder={{}}
orderByColumn={() => orderByColumn}
/>,

View file

@ -1,7 +1,7 @@
import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom';
import { TagsTableRow as createTagsTableRow } from '../../src/tags/TagsTableRow';
import { RoutesPrefixProvider } from '../../src/utils/routesPrefix';
import { renderWithEvents } from '../__helpers__/setUpTest';
import { colorGeneratorMock } from '../utils/services/__mocks__/ColorGenerator.mock';
@ -13,14 +13,15 @@ describe('<TagsTableRow />', () => {
);
const setUp = (tagStats?: { visits?: number; shortUrls?: number }) => renderWithEvents(
<MemoryRouter>
<table>
<tbody>
<TagsTableRow
tag={{ tag: 'foo&bar', visits: tagStats?.visits ?? 0, shortUrls: tagStats?.shortUrls ?? 0 }}
selectedServer={fromPartial({ id: 'abc123' })}
/>
</tbody>
</table>
<RoutesPrefixProvider value="/server/abc123">
<table>
<tbody>
<TagsTableRow
tag={{ tag: 'foo&bar', visits: tagStats?.visits ?? 0, shortUrls: tagStats?.shortUrls ?? 0 }}
/>
</tbody>
</table>
</RoutesPrefixProvider>
</MemoryRouter>,
);

View file

@ -2,6 +2,7 @@ import { screen } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn';
import { TagsSelector as createTagsSelector } from '../../../src/tags/helpers/TagsSelector';
import type { TagsList } from '../../../src/tags/reducers/tagsList';
import { SettingsProvider } from '../../../src/utils/settings';
import { renderWithEvents } from '../../__helpers__/setUpTest';
import { colorGeneratorMock } from '../../utils/services/__mocks__/ColorGenerator.mock';
@ -11,13 +12,14 @@ describe('<TagsSelector />', () => {
const tags = ['foo', 'bar'];
const tagsList = fromPartial<TagsList>({ tags: [...tags, 'baz'] });
const setUp = () => renderWithEvents(
<TagsSelector
selectedTags={tags}
tagsList={tagsList}
settings={fromPartial({})}
listTags={vi.fn()}
onChange={onChange}
/>,
<SettingsProvider value={fromPartial({})}>
<TagsSelector
selectedTags={tags}
tagsList={tagsList}
listTags={vi.fn()}
onChange={onChange}
/>
</SettingsProvider>,
);
it('has an input for tags', () => {

View file

@ -1,6 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import type { ShlinkApiClient } from '../../../src/api-contract';
import { tagDeleted, tagDeleteReducerCreator } from '../../../src/tags/reducers/tagDelete';
describe('tagDeleteReducer', () => {
@ -42,13 +41,12 @@ describe('tagDeleteReducer', () => {
describe('deleteTag', () => {
const dispatch = vi.fn();
const getState = () => fromPartial<ShlinkState>({});
it('calls API on success', async () => {
const tag = 'foo';
deleteTagsCall.mockResolvedValue(undefined);
await deleteTag(tag)(dispatch, getState, {});
await deleteTag(tag)(dispatch, vi.fn(), {});
expect(deleteTagsCall).toHaveBeenCalledTimes(1);
expect(deleteTagsCall).toHaveBeenNthCalledWith(1, [tag]);

View file

@ -1,6 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import type { ShlinkApiClient } from '../../../src/api-contract';
import { editTag as editTagCreator, tagEdited, tagEditReducerCreator } from '../../../src/tags/reducers/tagEdit';
import type { ColorGenerator } from '../../../src/utils/services/ColorGenerator';
@ -51,12 +50,11 @@ describe('tagEditReducer', () => {
describe('editTag', () => {
const dispatch = vi.fn();
const getState = () => fromPartial<ShlinkState>({});
it('calls API on success', async () => {
editTagCall.mockResolvedValue(undefined);
await editTag({ oldName, newName, color })(dispatch, getState, {});
await editTag({ oldName, newName, color })(dispatch, vi.fn(), {});
expect(editTagCall).toHaveBeenCalledTimes(1);
expect(editTagCall).toHaveBeenCalledWith(oldName, newName);

View file

@ -1,5 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkState } from '../../../../src/container/types';
import type { RootState } from '../../../src/container/store';
import type { ShortUrl } from '../../../src/short-urls/data';
import { createShortUrl as createShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlCreation';
import { tagDeleted } from '../../../src/tags/reducers/tagDelete';
@ -195,11 +195,11 @@ describe('tagsListReducer', () => {
describe('listTags', () => {
const dispatch = vi.fn();
const getState = vi.fn(() => fromPartial<ShlinkState>({}));
const getState = vi.fn(() => fromPartial<RootState>({}));
const listTagsMock = vi.fn();
const assertNoAction = async (tagsList: TagsList) => {
getState.mockReturnValue(fromPartial<ShlinkState>({ tagsList }));
getState.mockReturnValue(fromPartial<RootState>({ tagsList }));
await listTagsCreator(buildShlinkApiClient, false)()(dispatch, getState, {});
@ -218,7 +218,7 @@ describe('tagsListReducer', () => {
const tags = ['foo', 'bar', 'baz'];
listTagsMock.mockResolvedValue({ tags, stats: [] });
buildShlinkApiClient.mockReturnValue({ listTags: listTagsMock });
buildShlinkApiClient.mockReturnValue({ tagsStats: listTagsMock });
await listTags()(dispatch, getState, {});

View file

@ -1,23 +1,9 @@
import { addDays, formatISO, subDays } from 'date-fns';
import { formatDate, formatIsoDate, isBeforeOrEqual, isBetween, parseDate } from '../../../../src/utils/dates/helpers/date';
import { formatIsoDate, isBeforeOrEqual, isBetween, parseDate } from '../../../../src/utils/dates/helpers/date';
describe('date', () => {
const now = new Date();
describe('formatDate', () => {
it.each([
[parseDate('2020-03-05 10:00:10', 'yyyy-MM-dd HH:mm:ss'), 'dd/MM/yyyy', '05/03/2020'],
[parseDate('2020-03-05 10:00:10', 'yyyy-MM-dd HH:mm:ss'), 'yyyy-MM', '2020-03'],
[parseDate('2020-03-05 10:00:10', 'yyyy-MM-dd HH:mm:ss'), undefined, '2020-03-05'],
['2020-03-05 10:00:10', 'dd-MM-yyyy', '2020-03-05 10:00:10'],
['2020-03-05 10:00:10', undefined, '2020-03-05 10:00:10'],
[undefined, undefined, undefined],
[null, undefined, null],
])('formats date as expected', (date, format, expected) => {
expect(formatDate(format)(date)).toEqual(expected);
});
});
describe('formatIsoDate', () => {
it.each([
[

View file

@ -1,7 +1,7 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { LocalStorage } from '../../../../src/utils/services/LocalStorage';
import { MAIN_COLOR } from '../../../../src/utils/theme';
import { ColorGenerator } from '../../../src/utils/services/ColorGenerator';
import type { LocalStorage } from '../../../src/utils/services/LocalStorage';
describe('ColorGenerator', () => {
let colorGenerator: ColorGenerator;

View file

@ -1,5 +1,5 @@
import { render } from '@testing-library/react';
import type { OrderDir } from '../../../../shlink-frontend-kit/src/ordering/ordering';
import type { OrderDir } from '../../../../shlink-frontend-kit/src';
import { TableOrderIcon } from '../../../src/utils/table/TableOrderIcon';
describe('<TableOrderIcon />', () => {

View file

@ -3,6 +3,7 @@ import { fromPartial } from '@total-typescript/shoehorn';
import { formatISO } from 'date-fns';
import { MemoryRouter } from 'react-router-dom';
import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
import { SettingsProvider } from '../../src/utils/settings';
import { DomainVisits as createDomainVisits } from '../../src/visits/DomainVisits';
import type { DomainVisits } from '../../src/visits/reducers/domainVisits';
import { renderWithEvents } from '../__helpers__/setUpTest';
@ -20,13 +21,14 @@ describe('<DomainVisits />', () => {
const DomainVisits = createDomainVisits(fromPartial({ exportVisits }));
const setUp = () => renderWithEvents(
<MemoryRouter>
<DomainVisits
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getDomainVisits={getDomainVisits}
cancelGetDomainVisits={cancelGetDomainVisits}
domainVisits={domainVisits}
settings={fromPartial({})}
/>
<SettingsProvider value={fromPartial({})}>
<DomainVisits
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getDomainVisits={getDomainVisits}
cancelGetDomainVisits={cancelGetDomainVisits}
domainVisits={domainVisits}
/>
</SettingsProvider>
</MemoryRouter>,
);

View file

@ -3,6 +3,7 @@ import { fromPartial } from '@total-typescript/shoehorn';
import { formatISO } from 'date-fns';
import { MemoryRouter } from 'react-router-dom';
import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
import { SettingsProvider } from '../../src/utils/settings';
import { NonOrphanVisits as createNonOrphanVisits } from '../../src/visits/NonOrphanVisits';
import type { VisitsInfo } from '../../src/visits/reducers/types';
import { renderWithEvents } from '../__helpers__/setUpTest';
@ -15,13 +16,14 @@ describe('<NonOrphanVisits />', () => {
const NonOrphanVisits = createNonOrphanVisits(fromPartial({ exportVisits }));
const setUp = () => renderWithEvents(
<MemoryRouter>
<NonOrphanVisits
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getNonOrphanVisits={getNonOrphanVisits}
cancelGetNonOrphanVisits={cancelGetNonOrphanVisits}
nonOrphanVisits={nonOrphanVisits}
settings={fromPartial({})}
/>
<SettingsProvider value={fromPartial({})}>
<NonOrphanVisits
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getNonOrphanVisits={getNonOrphanVisits}
cancelGetNonOrphanVisits={cancelGetNonOrphanVisits}
nonOrphanVisits={nonOrphanVisits}
/>
</SettingsProvider>
</MemoryRouter>,
);

View file

@ -3,6 +3,7 @@ import { fromPartial } from '@total-typescript/shoehorn';
import { formatISO } from 'date-fns';
import { MemoryRouter } from 'react-router-dom';
import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
import { SettingsProvider } from '../../src/utils/settings';
import { OrphanVisits as createOrphanVisits } from '../../src/visits/OrphanVisits';
import type { VisitsInfo } from '../../src/visits/reducers/types';
import { renderWithEvents } from '../__helpers__/setUpTest';
@ -14,13 +15,14 @@ describe('<OrphanVisits />', () => {
const OrphanVisits = createOrphanVisits(fromPartial({ exportVisits }));
const setUp = () => renderWithEvents(
<MemoryRouter>
<OrphanVisits
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getOrphanVisits={getOrphanVisits}
orphanVisits={orphanVisits}
cancelGetOrphanVisits={vi.fn()}
settings={fromPartial({})}
/>
<SettingsProvider value={fromPartial({})}>
<OrphanVisits
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getOrphanVisits={getOrphanVisits}
orphanVisits={orphanVisits}
cancelGetOrphanVisits={vi.fn()}
/>
</SettingsProvider>
</MemoryRouter>,
);

View file

@ -4,6 +4,7 @@ import { formatISO } from 'date-fns';
import { identity } from 'ramda';
import { MemoryRouter } from 'react-router-dom';
import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
import { SettingsProvider } from '../../src/utils/settings';
import type { ShortUrlVisits as ShortUrlVisitsState } from '../../src/visits/reducers/shortUrlVisits';
import type { ShortUrlVisitsProps } from '../../src/visits/ShortUrlVisits';
import { ShortUrlVisits as createShortUrlVisits } from '../../src/visits/ShortUrlVisits';
@ -16,16 +17,17 @@ describe('<ShortUrlVisits />', () => {
const ShortUrlVisits = createShortUrlVisits(fromPartial({ exportVisits }));
const setUp = () => renderWithEvents(
<MemoryRouter>
<ShortUrlVisits
{...fromPartial<ShortUrlVisitsProps>({})}
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getShortUrlDetail={identity}
getShortUrlVisits={getShortUrlVisitsMock}
shortUrlVisits={shortUrlVisits}
shortUrlDetail={fromPartial({})}
settings={fromPartial({})}
cancelGetShortUrlVisits={() => {}}
/>
<SettingsProvider value={fromPartial({})}>
<ShortUrlVisits
{...fromPartial<ShortUrlVisitsProps>({})}
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getShortUrlDetail={identity}
getShortUrlVisits={getShortUrlVisitsMock}
shortUrlVisits={shortUrlVisits}
shortUrlDetail={fromPartial({})}
cancelGetShortUrlVisits={() => {}}
/>
</SettingsProvider>
</MemoryRouter>,
);

View file

@ -3,6 +3,7 @@ import { fromPartial } from '@total-typescript/shoehorn';
import { formatISO } from 'date-fns';
import { MemoryRouter } from 'react-router';
import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
import { SettingsProvider } from '../../src/utils/settings';
import type { TagVisits as TagVisitsStats } from '../../src/visits/reducers/tagVisits';
import type { TagVisitsProps } from '../../src/visits/TagVisits';
import { TagVisits as createTagVisits } from '../../src/visits/TagVisits';
@ -23,14 +24,15 @@ describe('<TagVisits />', () => {
);
const setUp = () => renderWithEvents(
<MemoryRouter>
<TagVisits
{...fromPartial<TagVisitsProps>({})}
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getTagVisits={getTagVisitsMock}
tagVisits={tagVisits}
settings={fromPartial({})}
cancelGetTagVisits={() => {}}
/>
<SettingsProvider value={fromPartial({})}>
<TagVisits
{...fromPartial<TagVisitsProps>({})}
{...fromPartial<MercureBoundProps>({ mercureInfo: {} })}
getTagVisits={getTagVisitsMock}
tagVisits={tagVisits}
cancelGetTagVisits={() => {}}
/>
</SettingsProvider>
</MemoryRouter>,
);

View file

@ -3,6 +3,7 @@ import { fromPartial } from '@total-typescript/shoehorn';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import { rangeOf } from '../../src/utils/helpers';
import { SettingsProvider } from '../../src/utils/settings';
import type { VisitsInfo } from '../../src/visits/reducers/types';
import type { Visit } from '../../src/visits/types';
import { VisitsStats } from '../../src/visits/VisitsStats';
@ -20,13 +21,14 @@ describe('<VisitsStats />', () => {
history,
...renderWithEvents(
<Router location={history.location} navigator={history}>
<VisitsStats
getVisits={getVisitsMock}
visitsInfo={fromPartial(visitsInfo)}
cancelGetVisits={() => {}}
settings={fromPartial({})}
exportCsv={exportCsv}
/>
<SettingsProvider value={fromPartial({})}>
<VisitsStats
getVisits={getVisitsMock}
visitsInfo={fromPartial(visitsInfo)}
cancelGetVisits={() => {}}
exportCsv={exportCsv}
/>
</SettingsProvider>
</Router>,
),
};

View file

@ -1,9 +1,8 @@
import { fromPartial } from '@total-typescript/shoehorn';
import { addDays, formatISO, subDays } from 'date-fns';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import { rangeOf } from '../../../../src/utils/utils';
import type { ShlinkVisits } from '../../../src/api/types';
import type { ShlinkApiClient, ShlinkVisits } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store';
import type { ShortUrl } from '../../../src/short-urls/data';
import { formatIsoDate } from '../../../src/utils/dates/helpers/date';
import type { DateInterval } from '../../../src/utils/dates/helpers/dateIntervals';
@ -151,7 +150,7 @@ describe('domainVisitsReducer', () => {
describe('getDomainVisits', () => {
const dispatchMock = vi.fn();
const getState = () => fromPartial<ShlinkState>({
const getState = () => fromPartial<RootState>({
domainVisits: { cancelLoad: false },
});
const domain = 'foo.com';

View file

@ -1,9 +1,8 @@
import { fromPartial } from '@total-typescript/shoehorn';
import { addDays, formatISO, subDays } from 'date-fns';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import { rangeOf } from '../../../../src/utils/utils';
import type { ShlinkVisits } from '../../../src/api/types';
import type { ShlinkApiClient, ShlinkVisits } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store';
import { formatIsoDate } from '../../../src/utils/dates/helpers/date';
import type { DateInterval } from '../../../src/utils/dates/helpers/dateIntervals';
import {
@ -118,7 +117,7 @@ describe('nonOrphanVisitsReducer', () => {
describe('getNonOrphanVisits', () => {
const dispatchMock = vi.fn();
const getState = () => fromPartial<ShlinkState>({
const getState = () => fromPartial<RootState>({
orphanVisits: { cancelLoad: false },
});

View file

@ -1,9 +1,8 @@
import { fromPartial } from '@total-typescript/shoehorn';
import { addDays, formatISO, subDays } from 'date-fns';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import { rangeOf } from '../../../../src/utils/utils';
import type { ShlinkVisits } from '../../../src/api/types';
import type { ShlinkApiClient, ShlinkVisits } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store';
import { formatIsoDate } from '../../../src/utils/dates/helpers/date';
import type { DateInterval } from '../../../src/utils/dates/helpers/dateIntervals';
import {
@ -118,7 +117,7 @@ describe('orphanVisitsReducer', () => {
describe('getOrphanVisits', () => {
const dispatchMock = vi.fn();
const getState = () => fromPartial<ShlinkState>({
const getState = () => fromPartial<RootState>({
orphanVisits: { cancelLoad: false },
});

View file

@ -1,9 +1,8 @@
import { fromPartial } from '@total-typescript/shoehorn';
import { addDays, formatISO, subDays } from 'date-fns';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import { rangeOf } from '../../../../src/utils/utils';
import type { ShlinkVisits } from '../../../src/api/types';
import type { ShlinkApiClient, ShlinkVisits } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store';
import { formatIsoDate } from '../../../src/utils/dates/helpers/date';
import type { DateInterval } from '../../../src/utils/dates/helpers/dateIntervals';
import type {
@ -142,7 +141,7 @@ describe('shortUrlVisitsReducer', () => {
describe('getShortUrlVisits', () => {
const dispatchMock = vi.fn();
const getState = () => fromPartial<ShlinkState>({
const getState = () => fromPartial<RootState>({
shortUrlVisits: { cancelLoad: false },
});

View file

@ -1,9 +1,8 @@
import { fromPartial } from '@total-typescript/shoehorn';
import { addDays, formatISO, subDays } from 'date-fns';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import { rangeOf } from '../../../../src/utils/utils';
import type { ShlinkVisits } from '../../../src/api/types';
import type { ShlinkApiClient, ShlinkVisits } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store';
import { formatIsoDate } from '../../../src/utils/dates/helpers/date';
import type { DateInterval } from '../../../src/utils/dates/helpers/dateIntervals';
import type {
@ -142,7 +141,7 @@ describe('tagVisitsReducer', () => {
describe('getTagVisits', () => {
const dispatchMock = vi.fn();
const getState = () => fromPartial<ShlinkState>({
const getState = () => fromPartial<RootState>({
tagVisits: { cancelLoad: false },
});
const tag = 'foo';

View file

@ -1,7 +1,6 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkApiClient } from '../../../../src/api/services/ShlinkApiClient';
import type { ShlinkState } from '../../../../src/container/types';
import type { ShlinkVisitsOverview } from '../../../src/api/types';
import type { ShlinkApiClient, ShlinkVisitsOverview } from '../../../src/api-contract';
import type { RootState } from '../../../src/container/store';
import { createNewVisits } from '../../../src/visits/reducers/visitCreation';
import type {
PartialVisitsSummary,
@ -25,7 +24,7 @@ describe('visitsOverviewReducer', () => {
it('returns loading on GET_OVERVIEW_START', () => {
const { loading } = reducer(
state({ loading: false, error: false }),
loadVisitsOverview.pending(''),
loadVisitsOverview.pending('', {}),
);
expect(loading).toEqual(true);
@ -34,7 +33,7 @@ describe('visitsOverviewReducer', () => {
it('stops loading and returns error on GET_OVERVIEW_ERROR', () => {
const { loading, error } = reducer(
state({ loading: true, error: false }),
loadVisitsOverview.rejected(null, ''),
loadVisitsOverview.rejected(null, '', {}),
);
expect(loading).toEqual(false);
@ -44,7 +43,7 @@ describe('visitsOverviewReducer', () => {
it('return visits overview on GET_OVERVIEW', () => {
const action = loadVisitsOverview.fulfilled(fromPartial({
nonOrphanVisits: { total: 100 },
}), 'requestId');
}), 'requestId', {});
const { loading, error, nonOrphanVisits } = reducer(state({ loading: true, error: false }), action);
expect(loading).toEqual(false);
@ -127,7 +126,7 @@ describe('visitsOverviewReducer', () => {
describe('loadVisitsOverview', () => {
const dispatchMock = vi.fn();
const getState = () => fromPartial<ShlinkState>({});
const getState = () => fromPartial<RootState>({});
it.each([
[
@ -155,7 +154,7 @@ describe('visitsOverviewReducer', () => {
const resolvedOverview = fromPartial<ShlinkVisitsOverview>(serverResult);
getVisitsOverview.mockResolvedValue(resolvedOverview);
await loadVisitsOverview()(dispatchMock, getState, {});
await loadVisitsOverview(buildApiClientMock)(dispatchMock, getState, {});
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(2, expect.objectContaining({ payload: dispatchedPayload }));

View file

@ -1,5 +1,5 @@
import { fromPartial } from '@total-typescript/shoehorn';
import type { ShlinkVisitsParams } from '../../../src/api/types';
import type { ShlinkVisitsParams } from '../../../src/api-contract';
import { formatIsoDate, parseDate } from '../../../src/utils/dates/helpers/date';
import type { CreateVisit, OrphanVisit, VisitsParams } from '../../../src/visits/types';
import type { GroupedNewVisits } from '../../../src/visits/types/helpers';

View file

@ -1,4 +1,4 @@
import { pipe } from 'ramda';
import { pipe, range } from 'ramda';
import type { SyntheticEvent } from 'react';
type Optional<T> = T | null | undefined;
@ -9,3 +9,6 @@ export const handleEventPreventingDefault = <T>(handler: () => T) => pipe(
(e: SyntheticEvent) => e.preventDefault(),
handler,
);
export const rangeOf = <T>(size: number, mappingFn: (value: number) => T, startAt = 1): T[] =>
range(startAt, size + 1).map(mappingFn);