mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-12-11 02:09:53 -06:00
Merge pull request #511 from acelaya-forks/feature/push-visits-in-date
Feature/push visits in date
This commit is contained in:
commit
bd0fca23cf
@ -20,7 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
* *Nothing*
|
* *Nothing*
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* *Nothing*
|
* [#252](https://github.com/shlinkio/shlink-web-client/issues/252) Fixed visits coming from mercure being added in real time, even when selected date interval does not match tha visit's date.
|
||||||
|
|
||||||
|
|
||||||
## [3.3.2] - 2021-10-17
|
## [3.3.2] - 2021-10-17
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { format, formatISO, parse } from 'date-fns';
|
import { format, formatISO, isAfter, isBefore, isWithinInterval, parse, parseISO as stdParseISO } from 'date-fns';
|
||||||
import { OptionalString } from '../utils';
|
import { OptionalString } from '../utils';
|
||||||
|
|
||||||
type DateOrString = Date | string;
|
type DateOrString = Date | string;
|
||||||
@ -21,3 +21,21 @@ export const formatIsoDate = (date?: NullableDate) => formatDateFromFormat(date,
|
|||||||
export const formatInternational = formatDate();
|
export const formatInternational = formatDate();
|
||||||
|
|
||||||
export const parseDate = (date: string, format: string) => parse(date, format, new Date());
|
export const parseDate = (date: string, format: string) => parse(date, format, new Date());
|
||||||
|
|
||||||
|
const parseISO = (date: DateOrString): Date => isDateObject(date) ? date : stdParseISO(date);
|
||||||
|
|
||||||
|
export const isBetween = (date: DateOrString, start?: DateOrString, end?: DateOrString): boolean => {
|
||||||
|
if (!start && end) {
|
||||||
|
return isBefore(parseISO(date), parseISO(end));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start && !end) {
|
||||||
|
return isAfter(parseISO(date), parseISO(start));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start && end) {
|
||||||
|
return isWithinInterval(parseISO(date), { start: parseISO(start), end: parseISO(end) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { GetState } from '../../container/types';
|
|||||||
import { ShlinkVisitsParams } from '../../api/types';
|
import { ShlinkVisitsParams } from '../../api/types';
|
||||||
import { isOrphanVisit } from '../types/helpers';
|
import { isOrphanVisit } from '../types/helpers';
|
||||||
import { ApiErrorAction } from '../../api/types/actions';
|
import { ApiErrorAction } from '../../api/types/actions';
|
||||||
|
import { isBetween } from '../../utils/helpers/date';
|
||||||
import { getVisitsWithLoader } from './common';
|
import { getVisitsWithLoader } from './common';
|
||||||
import { CREATE_VISITS, CreateVisitsAction } from './visitCreation';
|
import { CREATE_VISITS, CreateVisitsAction } from './visitCreation';
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ export const GET_ORPHAN_VISITS_PROGRESS_CHANGED = 'shlink/orphanVisits/GET_ORPHA
|
|||||||
|
|
||||||
export interface OrphanVisitsAction extends Action<string> {
|
export interface OrphanVisitsAction extends Action<string> {
|
||||||
visits: Visit[];
|
visits: Visit[];
|
||||||
|
query?: ShlinkVisitsParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrphanVisitsCombinedAction = OrphanVisitsAction
|
type OrphanVisitsCombinedAction = OrphanVisitsAction
|
||||||
@ -39,13 +41,16 @@ const initialState: VisitsInfo = {
|
|||||||
export default buildReducer<VisitsInfo, OrphanVisitsCombinedAction>({
|
export default buildReducer<VisitsInfo, OrphanVisitsCombinedAction>({
|
||||||
[GET_ORPHAN_VISITS_START]: () => ({ ...initialState, loading: true }),
|
[GET_ORPHAN_VISITS_START]: () => ({ ...initialState, loading: true }),
|
||||||
[GET_ORPHAN_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
[GET_ORPHAN_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||||
[GET_ORPHAN_VISITS]: (_, { visits }) => ({ ...initialState, visits }),
|
[GET_ORPHAN_VISITS]: (_, { visits, query }) => ({ ...initialState, visits, query }),
|
||||||
[GET_ORPHAN_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
|
[GET_ORPHAN_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
|
||||||
[GET_ORPHAN_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
[GET_ORPHAN_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
||||||
[GET_ORPHAN_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }),
|
[GET_ORPHAN_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }),
|
||||||
[CREATE_VISITS]: (state, { createdVisits }) => {
|
[CREATE_VISITS]: (state, { createdVisits }) => {
|
||||||
const { visits } = state;
|
const { visits, query = {} } = state;
|
||||||
const newVisits = createdVisits.map(({ visit }) => visit);
|
const { startDate, endDate } = query;
|
||||||
|
const newVisits = createdVisits
|
||||||
|
.filter(({ visit }) => isBetween(visit.date, startDate, endDate))
|
||||||
|
.map(({ visit }) => visit);
|
||||||
|
|
||||||
return { ...state, visits: [ ...newVisits, ...visits ] };
|
return { ...state, visits: [ ...newVisits, ...visits ] };
|
||||||
},
|
},
|
||||||
@ -66,6 +71,7 @@ export const getOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
|
|||||||
return { ...result, data: visits };
|
return { ...result, data: visits };
|
||||||
});
|
});
|
||||||
const shouldCancel = () => getState().orphanVisits.cancelLoad;
|
const shouldCancel = () => getState().orphanVisits.cancelLoad;
|
||||||
|
const extraFinishActionData: Partial<OrphanVisitsAction> = { query };
|
||||||
const actionMap = {
|
const actionMap = {
|
||||||
start: GET_ORPHAN_VISITS_START,
|
start: GET_ORPHAN_VISITS_START,
|
||||||
large: GET_ORPHAN_VISITS_LARGE,
|
large: GET_ORPHAN_VISITS_LARGE,
|
||||||
@ -74,7 +80,7 @@ export const getOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
|
|||||||
progress: GET_ORPHAN_VISITS_PROGRESS_CHANGED,
|
progress: GET_ORPHAN_VISITS_PROGRESS_CHANGED,
|
||||||
};
|
};
|
||||||
|
|
||||||
return getVisitsWithLoader(visitsLoader, {}, actionMap, dispatch, shouldCancel);
|
return getVisitsWithLoader(visitsLoader, extraFinishActionData, actionMap, dispatch, shouldCancel);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cancelGetOrphanVisits = buildActionCreator(GET_ORPHAN_VISITS_CANCEL);
|
export const cancelGetOrphanVisits = buildActionCreator(GET_ORPHAN_VISITS_CANCEL);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilde
|
|||||||
import { GetState } from '../../container/types';
|
import { GetState } from '../../container/types';
|
||||||
import { ShlinkVisitsParams } from '../../api/types';
|
import { ShlinkVisitsParams } from '../../api/types';
|
||||||
import { ApiErrorAction } from '../../api/types/actions';
|
import { ApiErrorAction } from '../../api/types/actions';
|
||||||
|
import { isBetween } from '../../utils/helpers/date';
|
||||||
import { getVisitsWithLoader } from './common';
|
import { getVisitsWithLoader } from './common';
|
||||||
import { CREATE_VISITS, CreateVisitsAction } from './visitCreation';
|
import { CREATE_VISITS, CreateVisitsAction } from './visitCreation';
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {}
|
|||||||
|
|
||||||
interface ShortUrlVisitsAction extends Action<string>, ShortUrlIdentifier {
|
interface ShortUrlVisitsAction extends Action<string>, ShortUrlIdentifier {
|
||||||
visits: Visit[];
|
visits: Visit[];
|
||||||
|
query?: ShlinkVisitsParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShortUrlVisitsCombinedAction = ShortUrlVisitsAction
|
type ShortUrlVisitsCombinedAction = ShortUrlVisitsAction
|
||||||
@ -33,7 +35,7 @@ type ShortUrlVisitsCombinedAction = ShortUrlVisitsAction
|
|||||||
const initialState: ShortUrlVisits = {
|
const initialState: ShortUrlVisits = {
|
||||||
visits: [],
|
visits: [],
|
||||||
shortCode: '',
|
shortCode: '',
|
||||||
domain: undefined,
|
domain: undefined, // Deprecated. Value from query params can be used instead
|
||||||
loading: false,
|
loading: false,
|
||||||
loadingLarge: false,
|
loadingLarge: false,
|
||||||
error: false,
|
error: false,
|
||||||
@ -44,22 +46,27 @@ const initialState: ShortUrlVisits = {
|
|||||||
export default buildReducer<ShortUrlVisits, ShortUrlVisitsCombinedAction>({
|
export default buildReducer<ShortUrlVisits, ShortUrlVisitsCombinedAction>({
|
||||||
[GET_SHORT_URL_VISITS_START]: () => ({ ...initialState, loading: true }),
|
[GET_SHORT_URL_VISITS_START]: () => ({ ...initialState, loading: true }),
|
||||||
[GET_SHORT_URL_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
[GET_SHORT_URL_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||||
[GET_SHORT_URL_VISITS]: (_, { visits, shortCode, domain }) => ({
|
[GET_SHORT_URL_VISITS]: (_, { visits, query, shortCode, domain }) => ({
|
||||||
...initialState,
|
...initialState,
|
||||||
visits,
|
visits,
|
||||||
shortCode,
|
shortCode,
|
||||||
domain,
|
domain,
|
||||||
|
query,
|
||||||
}),
|
}),
|
||||||
[GET_SHORT_URL_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
|
[GET_SHORT_URL_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
|
||||||
[GET_SHORT_URL_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
[GET_SHORT_URL_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
||||||
[GET_SHORT_URL_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }),
|
[GET_SHORT_URL_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }),
|
||||||
[CREATE_VISITS]: (state, { createdVisits }) => {
|
[CREATE_VISITS]: (state, { createdVisits }) => {
|
||||||
const { shortCode, domain, visits } = state;
|
const { shortCode, domain, visits, query = {} } = state;
|
||||||
|
const { startDate, endDate } = query;
|
||||||
const newVisits = createdVisits
|
const newVisits = createdVisits
|
||||||
.filter(({ shortUrl }) => shortUrl && shortUrlMatches(shortUrl, shortCode, domain))
|
.filter(
|
||||||
|
({ shortUrl, visit }) =>
|
||||||
|
shortUrl && shortUrlMatches(shortUrl, shortCode, domain) && isBetween(visit.date, startDate, endDate),
|
||||||
|
)
|
||||||
.map(({ visit }) => visit);
|
.map(({ visit }) => visit);
|
||||||
|
|
||||||
return { ...state, visits: [ ...newVisits, ...visits ] };
|
return newVisits.length === 0 ? state : { ...state, visits: [ ...newVisits, ...visits ] };
|
||||||
},
|
},
|
||||||
}, initialState);
|
}, initialState);
|
||||||
|
|
||||||
@ -73,7 +80,7 @@ export const getShortUrlVisits = (buildShlinkApiClient: ShlinkApiClientBuilder)
|
|||||||
{ ...query, page, itemsPerPage },
|
{ ...query, page, itemsPerPage },
|
||||||
);
|
);
|
||||||
const shouldCancel = () => getState().shortUrlVisits.cancelLoad;
|
const shouldCancel = () => getState().shortUrlVisits.cancelLoad;
|
||||||
const extraFinishActionData: Partial<ShortUrlVisitsAction> = { shortCode, domain: query.domain };
|
const extraFinishActionData: Partial<ShortUrlVisitsAction> = { shortCode, query, domain: query.domain };
|
||||||
const actionMap = {
|
const actionMap = {
|
||||||
start: GET_SHORT_URL_VISITS_START,
|
start: GET_SHORT_URL_VISITS_START,
|
||||||
large: GET_SHORT_URL_VISITS_LARGE,
|
large: GET_SHORT_URL_VISITS_LARGE,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilde
|
|||||||
import { GetState } from '../../container/types';
|
import { GetState } from '../../container/types';
|
||||||
import { ShlinkVisitsParams } from '../../api/types';
|
import { ShlinkVisitsParams } from '../../api/types';
|
||||||
import { ApiErrorAction } from '../../api/types/actions';
|
import { ApiErrorAction } from '../../api/types/actions';
|
||||||
|
import { isBetween } from '../../utils/helpers/date';
|
||||||
import { getVisitsWithLoader } from './common';
|
import { getVisitsWithLoader } from './common';
|
||||||
import { CREATE_VISITS, CreateVisitsAction } from './visitCreation';
|
import { CREATE_VISITS, CreateVisitsAction } from './visitCreation';
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ export interface TagVisits extends VisitsInfo {
|
|||||||
export interface TagVisitsAction extends Action<string> {
|
export interface TagVisitsAction extends Action<string> {
|
||||||
visits: Visit[];
|
visits: Visit[];
|
||||||
tag: string;
|
tag: string;
|
||||||
|
query?: ShlinkVisitsParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TagsVisitsCombinedAction = TagVisitsAction
|
type TagsVisitsCombinedAction = TagVisitsAction
|
||||||
@ -44,14 +46,15 @@ const initialState: TagVisits = {
|
|||||||
export default buildReducer<TagVisits, TagsVisitsCombinedAction>({
|
export default buildReducer<TagVisits, TagsVisitsCombinedAction>({
|
||||||
[GET_TAG_VISITS_START]: () => ({ ...initialState, loading: true }),
|
[GET_TAG_VISITS_START]: () => ({ ...initialState, loading: true }),
|
||||||
[GET_TAG_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
[GET_TAG_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||||
[GET_TAG_VISITS]: (_, { visits, tag }) => ({ ...initialState, visits, tag }),
|
[GET_TAG_VISITS]: (_, { visits, tag, query }) => ({ ...initialState, visits, tag, query }),
|
||||||
[GET_TAG_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
|
[GET_TAG_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
|
||||||
[GET_TAG_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
[GET_TAG_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
||||||
[GET_TAG_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }),
|
[GET_TAG_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }),
|
||||||
[CREATE_VISITS]: (state, { createdVisits }) => {
|
[CREATE_VISITS]: (state, { createdVisits }) => {
|
||||||
const { tag, visits } = state;
|
const { tag, visits, query = {} } = state;
|
||||||
|
const { startDate, endDate } = query;
|
||||||
const newVisits = createdVisits
|
const newVisits = createdVisits
|
||||||
.filter(({ shortUrl }) => shortUrl?.tags.includes(tag))
|
.filter(({ shortUrl, visit }) => shortUrl?.tags.includes(tag) && isBetween(visit.date, startDate, endDate))
|
||||||
.map(({ visit }) => visit);
|
.map(({ visit }) => visit);
|
||||||
|
|
||||||
return { ...state, visits: [ ...newVisits, ...visits ] };
|
return { ...state, visits: [ ...newVisits, ...visits ] };
|
||||||
@ -68,7 +71,7 @@ export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (
|
|||||||
{ ...query, page, itemsPerPage },
|
{ ...query, page, itemsPerPage },
|
||||||
);
|
);
|
||||||
const shouldCancel = () => getState().tagVisits.cancelLoad;
|
const shouldCancel = () => getState().tagVisits.cancelLoad;
|
||||||
const extraFinishActionData: Partial<TagVisitsAction> = { tag };
|
const extraFinishActionData: Partial<TagVisitsAction> = { tag, query };
|
||||||
const actionMap = {
|
const actionMap = {
|
||||||
start: GET_TAG_VISITS_START,
|
start: GET_TAG_VISITS_START,
|
||||||
large: GET_TAG_VISITS_LARGE,
|
large: GET_TAG_VISITS_LARGE,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
import { ShortUrl } from '../../short-urls/data';
|
import { ShortUrl } from '../../short-urls/data';
|
||||||
import { ProblemDetailsError } from '../../api/types';
|
import { ProblemDetailsError, ShlinkVisitsParams } from '../../api/types';
|
||||||
import { DateRange } from '../../utils/dates/types';
|
import { DateRange } from '../../utils/dates/types';
|
||||||
|
|
||||||
export interface VisitsInfo {
|
export interface VisitsInfo {
|
||||||
@ -11,6 +11,7 @@ export interface VisitsInfo {
|
|||||||
errorData?: ProblemDetailsError;
|
errorData?: ProblemDetailsError;
|
||||||
progress: number;
|
progress: number;
|
||||||
cancelLoad: boolean;
|
cancelLoad: boolean;
|
||||||
|
query?: ShlinkVisitsParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VisitsLoadProgressChangedAction extends Action<string> {
|
export interface VisitsLoadProgressChangedAction extends Action<string> {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
|
import { addDays, subDays } from 'date-fns';
|
||||||
import reducer, {
|
import reducer, {
|
||||||
getOrphanVisits,
|
getOrphanVisits,
|
||||||
cancelGetOrphanVisits,
|
cancelGetOrphanVisits,
|
||||||
@ -15,8 +16,10 @@ import { Visit, VisitsInfo } from '../../../src/visits/types';
|
|||||||
import { ShlinkVisits } from '../../../src/api/types';
|
import { ShlinkVisits } from '../../../src/api/types';
|
||||||
import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient';
|
import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient';
|
||||||
import { ShlinkState } from '../../../src/container/types';
|
import { ShlinkState } from '../../../src/container/types';
|
||||||
|
import { formatIsoDate } from '../../../src/utils/helpers/date';
|
||||||
|
|
||||||
describe('orphanVisitsReducer', () => {
|
describe('orphanVisitsReducer', () => {
|
||||||
|
const now = new Date();
|
||||||
const visitsMocks = rangeOf(2, () => Mock.all<Visit>());
|
const visitsMocks = rangeOf(2, () => Mock.all<Visit>());
|
||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
@ -64,15 +67,48 @@ describe('orphanVisitsReducer', () => {
|
|||||||
expect(visits).toEqual(actionVisits);
|
expect(visits).toEqual(actionVisits);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prepends new visits on CREATE_VISIT', () => {
|
it.each([
|
||||||
const prevState = buildState({ visits: visitsMocks });
|
[{}, visitsMocks.length + 2 ],
|
||||||
|
[
|
||||||
|
Mock.of<VisitsInfo>({
|
||||||
|
query: { endDate: formatIsoDate(subDays(now, 1)) ?? undefined },
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<VisitsInfo>({
|
||||||
|
query: { startDate: formatIsoDate(addDays(now, 1)) ?? undefined },
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<VisitsInfo>({
|
||||||
|
query: {
|
||||||
|
startDate: formatIsoDate(subDays(now, 5)) ?? undefined,
|
||||||
|
endDate: formatIsoDate(subDays(now, 2)) ?? undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<VisitsInfo>({
|
||||||
|
query: {
|
||||||
|
startDate: formatIsoDate(subDays(now, 5)) ?? undefined,
|
||||||
|
endDate: formatIsoDate(addDays(now, 3)) ?? undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visitsMocks.length + 2,
|
||||||
|
],
|
||||||
|
])('prepends new visits on CREATE_VISIT', (state, expectedVisits) => {
|
||||||
|
const prevState = buildState({ ...state, visits: visitsMocks });
|
||||||
|
const visit = Mock.of<Visit>({ date: formatIsoDate(now) ?? undefined });
|
||||||
|
|
||||||
const { visits } = reducer(
|
const { visits } = reducer(
|
||||||
prevState,
|
prevState,
|
||||||
{ type: CREATE_VISITS, createdVisits: [{ visit: {} }, { visit: {} }] } as any,
|
{ type: CREATE_VISITS, createdVisits: [{ visit }, { visit }] } as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(visits).toEqual([{}, {}, ...visitsMocks ]);
|
expect(visits).toHaveLength(expectedVisits);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns new progress on GET_ORPHAN_VISITS_PROGRESS_CHANGED', () => {
|
it('returns new progress on GET_ORPHAN_VISITS_PROGRESS_CHANGED', () => {
|
||||||
@ -124,7 +160,7 @@ describe('orphanVisitsReducer', () => {
|
|||||||
|
|
||||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_ORPHAN_VISITS_START });
|
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_ORPHAN_VISITS_START });
|
||||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_ORPHAN_VISITS, visits });
|
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_ORPHAN_VISITS, visits, query: query ?? {} });
|
||||||
expect(ShlinkApiClient.getOrphanVisits).toHaveBeenCalledTimes(1);
|
expect(ShlinkApiClient.getOrphanVisits).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
|
import { addDays, subDays } from 'date-fns';
|
||||||
import reducer, {
|
import reducer, {
|
||||||
getShortUrlVisits,
|
getShortUrlVisits,
|
||||||
cancelGetShortUrlVisits,
|
cancelGetShortUrlVisits,
|
||||||
@ -16,8 +17,10 @@ import { Visit } from '../../../src/visits/types';
|
|||||||
import { ShlinkVisits } from '../../../src/api/types';
|
import { ShlinkVisits } from '../../../src/api/types';
|
||||||
import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient';
|
import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient';
|
||||||
import { ShlinkState } from '../../../src/container/types';
|
import { ShlinkState } from '../../../src/container/types';
|
||||||
|
import { formatIsoDate } from '../../../src/utils/helpers/date';
|
||||||
|
|
||||||
describe('shortUrlVisitsReducer', () => {
|
describe('shortUrlVisitsReducer', () => {
|
||||||
|
const now = new Date();
|
||||||
const visitsMocks = rangeOf(2, () => Mock.all<Visit>());
|
const visitsMocks = rangeOf(2, () => Mock.all<Visit>());
|
||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
@ -66,8 +69,52 @@ describe('shortUrlVisitsReducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[{ shortCode: 'abc123' }, [{}, ...visitsMocks ]],
|
[{ shortCode: 'abc123' }, visitsMocks.length + 1 ],
|
||||||
[{ shortCode: 'def456' }, visitsMocks ],
|
[{ shortCode: 'def456' }, visitsMocks.length ],
|
||||||
|
[
|
||||||
|
Mock.of<ShortUrlVisits>({
|
||||||
|
shortCode: 'abc123',
|
||||||
|
query: { endDate: formatIsoDate(subDays(now, 1)) ?? undefined },
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<ShortUrlVisits>({
|
||||||
|
shortCode: 'abc123',
|
||||||
|
query: { startDate: formatIsoDate(addDays(now, 1)) ?? undefined },
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<ShortUrlVisits>({
|
||||||
|
shortCode: 'abc123',
|
||||||
|
query: {
|
||||||
|
startDate: formatIsoDate(subDays(now, 5)) ?? undefined,
|
||||||
|
endDate: formatIsoDate(subDays(now, 2)) ?? undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<ShortUrlVisits>({
|
||||||
|
shortCode: 'abc123',
|
||||||
|
query: {
|
||||||
|
startDate: formatIsoDate(subDays(now, 5)) ?? undefined,
|
||||||
|
endDate: formatIsoDate(addDays(now, 3)) ?? undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visitsMocks.length + 1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<ShortUrlVisits>({
|
||||||
|
shortCode: 'def456',
|
||||||
|
query: {
|
||||||
|
startDate: formatIsoDate(subDays(now, 5)) ?? undefined,
|
||||||
|
endDate: formatIsoDate(addDays(now, 3)) ?? undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
])('prepends new visits on CREATE_VISIT', (state, expectedVisits) => {
|
])('prepends new visits on CREATE_VISIT', (state, expectedVisits) => {
|
||||||
const shortUrl = {
|
const shortUrl = {
|
||||||
shortCode: 'abc123',
|
shortCode: 'abc123',
|
||||||
@ -77,9 +124,12 @@ describe('shortUrlVisitsReducer', () => {
|
|||||||
visits: visitsMocks,
|
visits: visitsMocks,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { visits } = reducer(prevState, { type: CREATE_VISITS, createdVisits: [{ shortUrl, visit: {} }] } as any);
|
const { visits } = reducer(
|
||||||
|
prevState,
|
||||||
|
{ type: CREATE_VISITS, createdVisits: [{ shortUrl, visit: { date: formatIsoDate(now) ?? undefined } }] } as any,
|
||||||
|
);
|
||||||
|
|
||||||
expect(visits).toEqual(expectedVisits);
|
expect(visits).toHaveLength(expectedVisits);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns new progress on GET_SHORT_URL_VISITS_PROGRESS_CHANGED', () => {
|
it('returns new progress on GET_SHORT_URL_VISITS_PROGRESS_CHANGED', () => {
|
||||||
@ -133,7 +183,10 @@ describe('shortUrlVisitsReducer', () => {
|
|||||||
|
|
||||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_SHORT_URL_VISITS_START });
|
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_SHORT_URL_VISITS_START });
|
||||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_SHORT_URL_VISITS, visits, shortCode, domain });
|
expect(dispatchMock).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
{ type: GET_SHORT_URL_VISITS, visits, shortCode, domain, query: query ?? {} },
|
||||||
|
);
|
||||||
expect(ShlinkApiClient.getShortUrlVisits).toHaveBeenCalledTimes(1);
|
expect(ShlinkApiClient.getShortUrlVisits).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
|
import { addDays, subDays } from 'date-fns';
|
||||||
import reducer, {
|
import reducer, {
|
||||||
getTagVisits,
|
getTagVisits,
|
||||||
cancelGetTagVisits,
|
cancelGetTagVisits,
|
||||||
@ -16,8 +17,10 @@ import { Visit } from '../../../src/visits/types';
|
|||||||
import { ShlinkVisits } from '../../../src/api/types';
|
import { ShlinkVisits } from '../../../src/api/types';
|
||||||
import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient';
|
import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient';
|
||||||
import { ShlinkState } from '../../../src/container/types';
|
import { ShlinkState } from '../../../src/container/types';
|
||||||
|
import { formatIsoDate } from '../../../src/utils/helpers/date';
|
||||||
|
|
||||||
describe('tagVisitsReducer', () => {
|
describe('tagVisitsReducer', () => {
|
||||||
|
const now = new Date();
|
||||||
const visitsMocks = rangeOf(2, () => Mock.all<Visit>());
|
const visitsMocks = rangeOf(2, () => Mock.all<Visit>());
|
||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
@ -66,8 +69,52 @@ describe('tagVisitsReducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[{ tag: 'foo' }, [{}, ...visitsMocks ]],
|
[{ tag: 'foo' }, visitsMocks.length + 1 ],
|
||||||
[{ tag: 'bar' }, visitsMocks ],
|
[{ tag: 'bar' }, visitsMocks.length ],
|
||||||
|
[
|
||||||
|
Mock.of<TagVisits>({
|
||||||
|
tag: 'foo',
|
||||||
|
query: { endDate: formatIsoDate(subDays(now, 1)) ?? undefined },
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<TagVisits>({
|
||||||
|
tag: 'foo',
|
||||||
|
query: { startDate: formatIsoDate(addDays(now, 1)) ?? undefined },
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<TagVisits>({
|
||||||
|
tag: 'foo',
|
||||||
|
query: {
|
||||||
|
startDate: formatIsoDate(subDays(now, 5)) ?? undefined,
|
||||||
|
endDate: formatIsoDate(subDays(now, 2)) ?? undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<TagVisits>({
|
||||||
|
tag: 'foo',
|
||||||
|
query: {
|
||||||
|
startDate: formatIsoDate(subDays(now, 5)) ?? undefined,
|
||||||
|
endDate: formatIsoDate(addDays(now, 3)) ?? undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visitsMocks.length + 1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Mock.of<TagVisits>({
|
||||||
|
tag: 'bar',
|
||||||
|
query: {
|
||||||
|
startDate: formatIsoDate(subDays(now, 5)) ?? undefined,
|
||||||
|
endDate: formatIsoDate(addDays(now, 3)) ?? undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
visitsMocks.length,
|
||||||
|
],
|
||||||
])('prepends new visits on CREATE_VISIT', (state, expectedVisits) => {
|
])('prepends new visits on CREATE_VISIT', (state, expectedVisits) => {
|
||||||
const shortUrl = {
|
const shortUrl = {
|
||||||
tags: [ 'foo', 'baz' ],
|
tags: [ 'foo', 'baz' ],
|
||||||
@ -77,9 +124,12 @@ describe('tagVisitsReducer', () => {
|
|||||||
visits: visitsMocks,
|
visits: visitsMocks,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { visits } = reducer(prevState, { type: CREATE_VISITS, createdVisits: [{ shortUrl, visit: {} }] } as any);
|
const { visits } = reducer(prevState, {
|
||||||
|
type: CREATE_VISITS,
|
||||||
|
createdVisits: [{ shortUrl, visit: { date: formatIsoDate(now) ?? undefined } }],
|
||||||
|
} as any);
|
||||||
|
|
||||||
expect(visits).toEqual(expectedVisits);
|
expect(visits).toHaveLength(expectedVisits);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns new progress on GET_TAG_VISITS_PROGRESS_CHANGED', () => {
|
it('returns new progress on GET_TAG_VISITS_PROGRESS_CHANGED', () => {
|
||||||
@ -132,7 +182,7 @@ describe('tagVisitsReducer', () => {
|
|||||||
|
|
||||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });
|
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });
|
||||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_TAG_VISITS, visits, tag });
|
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_TAG_VISITS, visits, tag, query: query ?? {} });
|
||||||
expect(ShlinkApiClient.getTagVisits).toHaveBeenCalledTimes(1);
|
expect(ShlinkApiClient.getTagVisits).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user