Migrated visits-loading actions to payload actions

This commit is contained in:
Alejandro Celaya 2022-11-12 10:33:52 +01:00
parent a6ed0c811d
commit a3cc3d5fc2
15 changed files with 136 additions and 127 deletions

View file

@ -17,9 +17,6 @@ export const buildReducer = <State, AT extends Action>(map: ActionHandlerMap<Sta
return actionHandler ? actionHandler(currentState, action) : currentState;
};
/** @deprecated */
export const buildActionCreator = <T extends string>(type: T) => (): Action<T> => ({ type });
export const createAsyncThunk = <Returned, ThunkArg>(
typePrefix: string,
payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, { state: ShlinkState }>,

View file

@ -5,7 +5,7 @@ import { ShlinkVisitsParams } from '../api/types';
import { Topics } from '../mercure/helpers/Topics';
import { useGoBack } from '../utils/helpers/hooks';
import { ReportExporter } from '../common/services/ReportExporter';
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
import { LoadTagVisits, TagVisits as TagVisitsState } from './reducers/tagVisits';
import { TagVisitsHeader } from './TagVisitsHeader';
import { VisitsStats } from './VisitsStats';
import { NormalizedVisit } from './types';
@ -13,7 +13,7 @@ import { CommonVisitsProps } from './types/CommonVisitsProps';
import { toApiParams } from './types/helpers';
export interface TagVisitsProps extends CommonVisitsProps {
getTagVisits: (tag: string, query?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void;
getTagVisits: (params: LoadTagVisits) => void;
tagVisits: TagVisitsState;
cancelGetTagVisits: () => void;
}
@ -28,7 +28,7 @@ export const TagVisits = (colorGenerator: ColorGenerator, { exportVisits }: Repo
const goBack = useGoBack();
const { tag = '' } = useParams();
const loadVisits = (params: ShlinkVisitsParams, doIntervalFallback?: boolean) =>
getTagVisits(tag, toApiParams(params), doIntervalFallback);
getTagVisits({ tag, query: toApiParams(params), doIntervalFallback });
const exportCsv = (visits: NormalizedVisit[]) => exportVisits(`tag_${tag}_visits.csv`, visits);
return (

View file

@ -1,11 +1,11 @@
import { flatten, prop, range, splitEvery } from 'ramda';
import { Action, Dispatch } from 'redux';
import { Dispatch } from '@reduxjs/toolkit';
import { ShlinkPaginator, ShlinkVisits, ShlinkVisitsParams } from '../../api/types';
import { Visit } from '../types';
import { parseApiError } from '../../api/utils';
import { ApiErrorAction } from '../../api/types/actions';
import { dateToMatchingInterval } from '../../utils/dates/types';
import { VisitsLoadProgressChangedAction } from './types';
import { VisitsLoaded, VisitsLoadProgressChangedAction } from './types';
const ITEMS_PER_PAGE = 5000;
const PARALLEL_REQUESTS_COUNT = 4;
@ -17,10 +17,10 @@ const calcProgress = (total: number, current: number): number => (current * 100)
type VisitsLoader = (page: number, itemsPerPage: number) => Promise<ShlinkVisits>;
type LastVisitLoader = () => Promise<Visit | undefined>;
export const getVisitsWithLoader = async <T extends Action<string> & { visits: Visit[] }>(
export const getVisitsWithLoader = async <T extends VisitsLoaded>(
visitsLoader: VisitsLoader,
lastVisitLoader: LastVisitLoader,
extraFinishActionData: Partial<T>,
extraFulfilledPayload: Partial<T>,
actionsPrefix: string,
dispatch: Dispatch,
shouldCancel: () => boolean,
@ -74,7 +74,7 @@ export const getVisitsWithLoader = async <T extends Action<string> & { visits: V
dispatch(
!visits.length && lastVisit
? { type: `${actionsPrefix}/fallbackToInterval`, payload: dateToMatchingInterval(lastVisit.date) }
: { ...extraFinishActionData, visits, type: `${actionsPrefix}/fulfilled` },
: { type: `${actionsPrefix}/fulfilled`, payload: { ...extraFulfilledPayload, visits } },
);
} catch (e: any) {
dispatch<ApiErrorAction>({ type: `${actionsPrefix}/rejected`, errorData: parseApiError(e) });
@ -89,5 +89,5 @@ export const lastVisitLoaderForLoader = (
return async () => Promise.resolve(undefined);
}
return async () => loader({ page: 1, itemsPerPage: 1 }).then((result) => result.data[0]);
return async () => loader({ page: 1, itemsPerPage: 1 }).then(({ data }) => data[0]);
};

View file

@ -1,16 +1,21 @@
import { createAction } from '@reduxjs/toolkit';
import { Action, Dispatch } from 'redux';
import { Visit } from '../types';
import { Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { GetState } from '../../container/types';
import { ShlinkVisitsParams } from '../../api/types';
import { ApiErrorAction } from '../../api/types/actions';
import { isBetween } from '../../utils/helpers/date';
import { getVisitsWithLoader, lastVisitLoaderForLoader } from './common';
import { createNewVisits, CreateVisitsAction } from './visitCreation';
import { domainMatches } from '../../short-urls/helpers';
import { LoadVisits, VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
import {
LoadVisits,
VisitsFallbackIntervalAction,
VisitsInfo,
VisitsLoaded,
VisitsLoadedAction,
VisitsLoadProgressChangedAction,
} from './types';
const REDUCER_PREFIX = 'shlink/domainVisits';
export const GET_DOMAIN_VISITS_START = `${REDUCER_PREFIX}/getDomainVisits/pending`;
@ -23,19 +28,15 @@ export const GET_DOMAIN_VISITS_FALLBACK_TO_INTERVAL = `${REDUCER_PREFIX}/getDoma
export const DEFAULT_DOMAIN = 'DEFAULT';
export interface DomainVisits extends VisitsInfo {
interface WithDomain {
domain: string;
}
export interface LoadDomainVisits extends LoadVisits {
domain: string;
}
export interface DomainVisits extends VisitsInfo, WithDomain {}
export interface DomainVisitsAction extends Action<string> {
visits: Visit[];
domain: string;
query?: ShlinkVisitsParams;
}
export interface LoadDomainVisits extends LoadVisits, WithDomain {}
type DomainVisitsAction = VisitsLoadedAction<WithDomain>;
type DomainVisitsCombinedAction = DomainVisitsAction
& VisitsLoadProgressChangedAction
@ -56,8 +57,8 @@ const initialState: DomainVisits = {
export default buildReducer<DomainVisits, DomainVisitsCombinedAction>({
[`${REDUCER_PREFIX}/getDomainVisits/pending`]: () => ({ ...initialState, loading: true }),
[`${REDUCER_PREFIX}/getDomainVisits/rejected`]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
[`${REDUCER_PREFIX}/getDomainVisits/fulfilled`]: (state, { visits, domain, query }) => (
{ ...state, visits, domain, query, loading: false, loadingLarge: false, error: false }
[`${REDUCER_PREFIX}/getDomainVisits/fulfilled`]: (state, { payload }: DomainVisitsAction) => (
{ ...state, ...payload, loading: false, loadingLarge: false, error: false }
),
[`${REDUCER_PREFIX}/getDomainVisits/large`]: (state) => ({ ...state, loadingLarge: true }),
[`${REDUCER_PREFIX}/getDomainVisits/cancel`]: (state) => ({ ...state, cancelLoad: true }),
@ -87,7 +88,7 @@ export const getDomainVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
);
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, async (params) => getVisits(domain, params));
const shouldCancel = () => getState().domainVisits.cancelLoad;
const extraFinishActionData: Partial<DomainVisitsAction> = { domain, query };
const extraFinishActionData: Partial<VisitsLoaded<WithDomain>> = { domain, query };
const prefix = `${REDUCER_PREFIX}/getDomainVisits`;
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);

View file

@ -1,15 +1,20 @@
import { createAction } from '@reduxjs/toolkit';
import { Action, Dispatch } from 'redux';
import { Visit } from '../types';
import { Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { GetState } from '../../container/types';
import { ShlinkVisitsParams } from '../../api/types';
import { ApiErrorAction } from '../../api/types/actions';
import { isBetween } from '../../utils/helpers/date';
import { getVisitsWithLoader, lastVisitLoaderForLoader } from './common';
import { createNewVisits, CreateVisitsAction } from './visitCreation';
import { LoadVisits, VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
import {
LoadVisits,
VisitsFallbackIntervalAction,
VisitsInfo,
VisitsLoaded,
VisitsLoadedAction,
VisitsLoadProgressChangedAction,
} from './types';
const REDUCER_PREFIX = 'shlink/orphanVisits';
export const GET_NON_ORPHAN_VISITS_START = `${REDUCER_PREFIX}/getNonOrphanVisits/pending`;
@ -20,12 +25,7 @@ export const GET_NON_ORPHAN_VISITS_CANCEL = `${REDUCER_PREFIX}/getNonOrphanVisit
export const GET_NON_ORPHAN_VISITS_PROGRESS_CHANGED = `${REDUCER_PREFIX}/getNonOrphanVisits/progressChanged`;
export const GET_NON_ORPHAN_VISITS_FALLBACK_TO_INTERVAL = `${REDUCER_PREFIX}/getNonOrphanVisits/fallbackToInterval`;
export interface NonOrphanVisitsAction extends Action<string> {
visits: Visit[];
query?: ShlinkVisitsParams;
}
type NonOrphanVisitsCombinedAction = NonOrphanVisitsAction
type NonOrphanVisitsCombinedAction = VisitsLoadedAction
& VisitsLoadProgressChangedAction
& VisitsFallbackIntervalAction
& CreateVisitsAction
@ -45,8 +45,8 @@ export default buildReducer<VisitsInfo, NonOrphanVisitsCombinedAction>({
[`${REDUCER_PREFIX}/getNonOrphanVisits/rejected`]: (_, { errorData }) => (
{ ...initialState, error: true, errorData }
),
[`${REDUCER_PREFIX}/getNonOrphanVisits/fulfilled`]: (state, { visits, query }) => (
{ ...state, visits, query, loading: false, loadingLarge: false, error: false }
[`${REDUCER_PREFIX}/getNonOrphanVisits/fulfilled`]: (state, { payload }: VisitsLoadedAction) => (
{ ...state, ...payload, loading: false, loadingLarge: false, error: false }
),
[`${REDUCER_PREFIX}/getNonOrphanVisits/large`]: (state) => ({ ...state, loadingLarge: true }),
[`${REDUCER_PREFIX}/getNonOrphanVisits/cancel`]: (state) => ({ ...state, cancelLoad: true }),
@ -73,7 +73,7 @@ export const getNonOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder)
shlinkGetNonOrphanVisits({ ...query, page, itemsPerPage });
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, shlinkGetNonOrphanVisits);
const shouldCancel = () => getState().orphanVisits.cancelLoad;
const extraFinishActionData: Partial<NonOrphanVisitsAction> = { query };
const extraFinishActionData: Partial<VisitsLoaded> = { query };
const prefix = `${REDUCER_PREFIX}/getNonOrphanVisits`;
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);

View file

@ -1,16 +1,22 @@
import { createAction } from '@reduxjs/toolkit';
import { Action, Dispatch } from 'redux';
import { OrphanVisit, OrphanVisitType, Visit } from '../types';
import { Dispatch } from 'redux';
import { OrphanVisit, OrphanVisitType } from '../types';
import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { GetState } from '../../container/types';
import { ShlinkVisitsParams } from '../../api/types';
import { isOrphanVisit } from '../types/helpers';
import { ApiErrorAction } from '../../api/types/actions';
import { isBetween } from '../../utils/helpers/date';
import { getVisitsWithLoader, lastVisitLoaderForLoader } from './common';
import { createNewVisits, CreateVisitsAction } from './visitCreation';
import { LoadVisits, VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
import {
LoadVisits,
VisitsFallbackIntervalAction,
VisitsInfo,
VisitsLoaded,
VisitsLoadedAction,
VisitsLoadProgressChangedAction,
} from './types';
const REDUCER_PREFIX = 'shlink/orphanVisits';
export const GET_ORPHAN_VISITS_START = `${REDUCER_PREFIX}/getOrphanVisits/pending`;
@ -21,16 +27,11 @@ export const GET_ORPHAN_VISITS_CANCEL = `${REDUCER_PREFIX}/getOrphanVisits/cance
export const GET_ORPHAN_VISITS_PROGRESS_CHANGED = `${REDUCER_PREFIX}/getOrphanVisits/progressChanged`;
export const GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL = `${REDUCER_PREFIX}/getOrphanVisits/fallbackToInterval`;
export interface OrphanVisitsAction extends Action<string> {
visits: Visit[];
query?: ShlinkVisitsParams;
}
export interface LoadOrphanVisits extends LoadVisits {
orphanVisitsType?: OrphanVisitType;
}
type OrphanVisitsCombinedAction = OrphanVisitsAction
type OrphanVisitsCombinedAction = VisitsLoadedAction
& VisitsLoadProgressChangedAction
& VisitsFallbackIntervalAction
& CreateVisitsAction
@ -48,8 +49,8 @@ const initialState: VisitsInfo = {
export default buildReducer<VisitsInfo, OrphanVisitsCombinedAction>({
[`${REDUCER_PREFIX}/getOrphanVisits/pending`]: () => ({ ...initialState, loading: true }),
[`${REDUCER_PREFIX}/getOrphanVisits/rejected`]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
[`${REDUCER_PREFIX}/getOrphanVisits/fulfilled`]: (state, { visits, query }) => (
{ ...state, visits, query, loading: false, loadingLarge: false, error: false }
[`${REDUCER_PREFIX}/getOrphanVisits/fulfilled`]: (state, { payload }: VisitsLoadedAction) => (
{ ...state, ...payload, loading: false, loadingLarge: false, error: false }
),
[`${REDUCER_PREFIX}/getOrphanVisits/large`]: (state) => ({ ...state, loadingLarge: true }),
[`${REDUCER_PREFIX}/getOrphanVisits/cancel`]: (state) => ({ ...state, cancelLoad: true }),
@ -83,7 +84,7 @@ export const getOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
});
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, getVisits);
const shouldCancel = () => getState().orphanVisits.cancelLoad;
const extraFinishActionData: Partial<OrphanVisitsAction> = { query };
const extraFinishActionData: Partial<VisitsLoaded> = { query };
const prefix = `${REDUCER_PREFIX}/getOrphanVisits`;
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);

View file

@ -1,17 +1,22 @@
import { createAction } from '@reduxjs/toolkit';
import { Action, Dispatch } from 'redux';
import { Dispatch } from 'redux';
import { shortUrlMatches } from '../../short-urls/helpers';
import { Visit } from '../types';
import { ShortUrlIdentifier } from '../../short-urls/data';
import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { GetState } from '../../container/types';
import { ShlinkVisitsParams } from '../../api/types';
import { ApiErrorAction } from '../../api/types/actions';
import { isBetween } from '../../utils/helpers/date';
import { getVisitsWithLoader, lastVisitLoaderForLoader } from './common';
import { createNewVisits, CreateVisitsAction } from './visitCreation';
import { LoadVisits, VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
import {
LoadVisits,
VisitsFallbackIntervalAction,
VisitsInfo,
VisitsLoaded,
VisitsLoadedAction,
VisitsLoadProgressChangedAction,
} from './types';
const REDUCER_PREFIX = 'shlink/shortUrlVisits';
export const GET_SHORT_URL_VISITS_START = `${REDUCER_PREFIX}/getShortUrlVisits/pending`;
@ -24,10 +29,7 @@ export const GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL = `${REDUCER_PREFIX}/getS
export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {}
interface ShortUrlVisitsAction extends Action<string>, ShortUrlIdentifier {
visits: Visit[];
query?: ShlinkVisitsParams;
}
type ShortUrlVisitsAction = VisitsLoadedAction<ShortUrlIdentifier>;
export interface LoadShortUrlVisits extends LoadVisits {
shortCode: string;
@ -53,12 +55,9 @@ const initialState: ShortUrlVisits = {
export default buildReducer<ShortUrlVisits, ShortUrlVisitsCombinedAction>({
[`${REDUCER_PREFIX}/getShortUrlVisits/pending`]: () => ({ ...initialState, loading: true }),
[`${REDUCER_PREFIX}/getShortUrlVisits/rejected`]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
[`${REDUCER_PREFIX}/getShortUrlVisits/fulfilled`]: (state, { visits, query, shortCode, domain }) => ({
[`${REDUCER_PREFIX}/getShortUrlVisits/fulfilled`]: (state, { payload }: ShortUrlVisitsAction) => ({
...state,
visits,
shortCode,
domain,
query,
...payload,
loading: false,
loadingLarge: false,
error: false,
@ -96,7 +95,7 @@ export const getShortUrlVisits = (buildShlinkApiClient: ShlinkApiClientBuilder)
async (params) => shlinkGetShortUrlVisits(shortCode, { ...params, domain: query.domain }),
);
const shouldCancel = () => getState().shortUrlVisits.cancelLoad;
const extraFinishActionData: Partial<ShortUrlVisitsAction> = { shortCode, query, domain: query.domain };
const extraFinishActionData: Partial<VisitsLoaded<ShortUrlIdentifier>> = { shortCode, query, domain: query.domain };
const prefix = `${REDUCER_PREFIX}/getShortUrlVisits`;
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);

View file

@ -1,15 +1,20 @@
import { createAction } from '@reduxjs/toolkit';
import { Action, Dispatch } from 'redux';
import { Visit } from '../types';
import { Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { GetState } from '../../container/types';
import { ShlinkVisitsParams } from '../../api/types';
import { ApiErrorAction } from '../../api/types/actions';
import { isBetween } from '../../utils/helpers/date';
import { getVisitsWithLoader, lastVisitLoaderForLoader } from './common';
import { createNewVisits, CreateVisitsAction } from './visitCreation';
import { VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
import {
LoadVisits,
VisitsFallbackIntervalAction,
VisitsInfo,
VisitsLoaded,
VisitsLoadedAction,
VisitsLoadProgressChangedAction,
} from './types';
const REDUCER_PREFIX = 'shlink/tagVisits';
export const GET_TAG_VISITS_START = `${REDUCER_PREFIX}/getTagVisits/pending`;
@ -24,12 +29,12 @@ export interface TagVisits extends VisitsInfo {
tag: string;
}
export interface TagVisitsAction extends Action<string> {
visits: Visit[];
export interface LoadTagVisits extends LoadVisits {
tag: string;
query?: ShlinkVisitsParams;
}
export type TagVisitsAction = VisitsLoadedAction<{ tag: string }>;
type TagsVisitsCombinedAction = TagVisitsAction
& VisitsLoadProgressChangedAction
& VisitsFallbackIntervalAction
@ -49,8 +54,8 @@ const initialState: TagVisits = {
export default buildReducer<TagVisits, TagsVisitsCombinedAction>({
[`${REDUCER_PREFIX}/getTagVisits/pending`]: () => ({ ...initialState, loading: true }),
[`${REDUCER_PREFIX}/getTagVisits/rejected`]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
[`${REDUCER_PREFIX}/getTagVisits/fulfilled`]: (state, { visits, tag, query }) => (
{ ...state, visits, tag, query, loading: false, loadingLarge: false, error: false }
[`${REDUCER_PREFIX}/getTagVisits/fulfilled`]: (state, { payload }: TagVisitsAction) => (
{ ...state, ...payload, loading: false, loadingLarge: false, error: false }
),
[`${REDUCER_PREFIX}/getTagVisits/large`]: (state) => ({ ...state, loadingLarge: true }),
[`${REDUCER_PREFIX}/getTagVisits/cancel`]: (state) => ({ ...state, cancelLoad: true }),
@ -70,9 +75,7 @@ export default buildReducer<TagVisits, TagsVisitsCombinedAction>({
}, initialState);
export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (
tag: string,
query: ShlinkVisitsParams = {},
doIntervalFallback = false,
{ tag, query = {}, doIntervalFallback = false }: LoadTagVisits,
) => async (dispatch: Dispatch, getState: GetState) => {
const { getTagVisits: getVisits } = buildShlinkApiClient(getState);
const visitsLoader = async (page: number, itemsPerPage: number) => getVisits(
@ -81,7 +84,7 @@ export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (
);
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, async (params) => getVisits(tag, params));
const shouldCancel = () => getState().tagVisits.cancelLoad;
const extraFinishActionData: Partial<TagVisitsAction> = { tag, query };
const extraFinishActionData: Partial<VisitsLoaded<{ tag: string }>> = { tag, query };
const prefix = `${REDUCER_PREFIX}/getTagVisits`;
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);

View file

@ -21,6 +21,13 @@ export interface LoadVisits {
doIntervalFallback?: boolean;
}
export type VisitsLoaded<T = {}> = T & {
visits: Visit[];
query?: ShlinkVisitsParams;
};
export type VisitsLoadedAction<T = {}> = PayloadAction<VisitsLoaded<T>>;
export type VisitsLoadProgressChangedAction = PayloadAction<number>;
export type VisitsFallbackIntervalAction = PayloadAction<DateInterval>;

View file

@ -1,22 +1,9 @@
import { Action } from 'redux';
import { buildActionCreator, buildReducer } from '../../../src/utils/helpers/redux';
import { buildReducer } from '../../../src/utils/helpers/redux';
describe('redux', () => {
beforeEach(jest.clearAllMocks);
describe('buildActionCreator', () => {
it.each([
['foo', { type: 'foo' }],
['bar', { type: 'bar' }],
['something', { type: 'something' }],
])('returns an action creator', (type, expected) => {
const actionCreator = buildActionCreator(type);
expect(actionCreator).toBeInstanceOf(Function);
expect(actionCreator()).toEqual(expected);
});
});
describe('buildReducer', () => {
const fooActionHandler = jest.fn(() => 'foo result');
const barActionHandler = jest.fn(() => 'bar result');

View file

@ -61,10 +61,10 @@ describe('domainVisitsReducer', () => {
it('return visits on GET_DOMAIN_VISITS', () => {
const actionVisits = [{}, {}];
const state = reducer(
buildState({ loading: true, error: false }),
{ type: GET_DOMAIN_VISITS, visits: actionVisits } as any,
);
const state = reducer(buildState({ loading: true, error: false }), {
type: GET_DOMAIN_VISITS,
payload: { visits: actionVisits },
} as any);
const { loading, error, visits } = state;
expect(loading).toEqual(false);
@ -201,7 +201,10 @@ describe('domainVisitsReducer', () => {
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_DOMAIN_VISITS_START });
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_DOMAIN_VISITS, visits, domain, query: query ?? {} });
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
type: GET_DOMAIN_VISITS,
payload: { visits, domain, query: query ?? {} },
});
expect(shlinkApiClient.getDomainVisits).toHaveBeenCalledTimes(1);
});

View file

@ -59,10 +59,10 @@ describe('nonOrphanVisitsReducer', () => {
it('return visits on GET_NON_ORPHAN_VISITS', () => {
const actionVisits = [{}, {}];
const state = reducer(
buildState({ loading: true, error: false }),
{ type: GET_NON_ORPHAN_VISITS, visits: actionVisits } as any,
);
const state = reducer(buildState({ loading: true, error: false }), {
type: GET_NON_ORPHAN_VISITS,
payload: { visits: actionVisits },
} as any);
const { loading, error, visits } = state;
expect(loading).toEqual(false);
@ -173,7 +173,10 @@ describe('nonOrphanVisitsReducer', () => {
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_NON_ORPHAN_VISITS_START });
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_NON_ORPHAN_VISITS, visits, query: query ?? {} });
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
type: GET_NON_ORPHAN_VISITS,
payload: { visits, query: query ?? {} },
});
expect(ShlinkApiClient.getNonOrphanVisits).toHaveBeenCalledTimes(1);
});

View file

@ -59,10 +59,10 @@ describe('orphanVisitsReducer', () => {
it('return visits on GET_ORPHAN_VISITS', () => {
const actionVisits = [{}, {}];
const state = reducer(
buildState({ loading: true, error: false }),
{ type: GET_ORPHAN_VISITS, visits: actionVisits } as any,
);
const state = reducer(buildState({ loading: true, error: false }), {
type: GET_ORPHAN_VISITS,
payload: { visits: actionVisits },
} as any);
const { loading, error, visits } = state;
expect(loading).toEqual(false);
@ -173,7 +173,10 @@ describe('orphanVisitsReducer', () => {
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_ORPHAN_VISITS_START });
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_ORPHAN_VISITS, visits, query: query ?? {} });
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
type: GET_ORPHAN_VISITS,
payload: { visits, query: query ?? {} },
});
expect(ShlinkApiClient.getOrphanVisits).toHaveBeenCalledTimes(1);
});

View file

@ -59,10 +59,10 @@ describe('shortUrlVisitsReducer', () => {
it('return visits on GET_SHORT_URL_VISITS', () => {
const actionVisits = [{}, {}];
const state = reducer(
buildState({ loading: true, error: false }),
{ type: GET_SHORT_URL_VISITS, visits: actionVisits } as any,
);
const state = reducer(buildState({ loading: true, error: false }), {
type: GET_SHORT_URL_VISITS,
payload: { visits: actionVisits },
} as any);
const { loading, error, visits } = state;
expect(loading).toEqual(false);
@ -195,10 +195,10 @@ describe('shortUrlVisitsReducer', () => {
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_SHORT_URL_VISITS_START });
expect(dispatchMock).toHaveBeenNthCalledWith(
2,
{ type: GET_SHORT_URL_VISITS, visits, shortCode, domain, query: query ?? {} },
);
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
type: GET_SHORT_URL_VISITS,
payload: { visits, shortCode, domain, query: query ?? {} },
});
expect(ShlinkApiClient.getShortUrlVisits).toHaveBeenCalledTimes(1);
});
@ -218,7 +218,9 @@ describe('shortUrlVisitsReducer', () => {
expect(ShlinkApiClient.getShortUrlVisits).toHaveBeenCalledTimes(expectedRequests);
expect(dispatchMock).toHaveBeenNthCalledWith(3, expect.objectContaining({
payload: expect.objectContaining({
visits: [...visitsMocks, ...visitsMocks, ...visitsMocks],
}),
}));
});

View file

@ -59,10 +59,10 @@ describe('tagVisitsReducer', () => {
it('return visits on GET_TAG_VISITS', () => {
const actionVisits = [{}, {}];
const state = reducer(
buildState({ loading: true, error: false }),
{ type: GET_TAG_VISITS, visits: actionVisits } as any,
);
const state = reducer(buildState({ loading: true, error: false }), {
type: GET_TAG_VISITS,
payload: { visits: actionVisits },
} as any);
const { loading, error, visits } = state;
expect(loading).toEqual(false);
@ -165,7 +165,7 @@ describe('tagVisitsReducer', () => {
it('dispatches start and error when promise is rejected', async () => {
const shlinkApiClient = buildApiClientMock(Promise.reject(new Error()));
await getTagVisits(() => shlinkApiClient)('foo')(dispatchMock, getState);
await getTagVisits(() => shlinkApiClient)({ tag })(dispatchMock, getState);
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });
@ -187,11 +187,14 @@ describe('tagVisitsReducer', () => {
},
}));
await getTagVisits(() => shlinkApiClient)(tag, query)(dispatchMock, getState);
await getTagVisits(() => shlinkApiClient)({ tag, query })(dispatchMock, getState);
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_TAG_VISITS, visits, tag, query: query ?? {} });
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
type: GET_TAG_VISITS,
payload: { visits, tag, query: query ?? {} },
});
expect(shlinkApiClient.getTagVisits).toHaveBeenCalledTimes(1);
});
@ -219,7 +222,7 @@ describe('tagVisitsReducer', () => {
.mockResolvedValueOnce(buildVisitsResult(lastVisits));
const ShlinkApiClient = Mock.of<ShlinkApiClient>({ getTagVisits: getShlinkTagVisits });
await getTagVisits(() => ShlinkApiClient)(tag, {}, true)(dispatchMock, getState);
await getTagVisits(() => ShlinkApiClient)({ tag, doIntervalFallback: true })(dispatchMock, getState);
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });