From e135dd92ec4e10478c326cc007f1649ba8ee0e98 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Oct 2021 10:31:32 +0200 Subject: [PATCH 1/2] Ensured new visits are pushed to the state only if they match selected date range --- src/utils/helpers/date.ts | 24 ++++++++++++++++++++- src/visits/reducers/orphanVisits.ts | 14 ++++++++---- src/visits/reducers/shortUrlVisits.ts | 19 ++++++++++------ src/visits/reducers/tagVisits.ts | 11 ++++++---- src/visits/types/index.ts | 3 ++- test/visits/reducers/orphanVisits.test.ts | 2 +- test/visits/reducers/shortUrlVisits.test.ts | 5 ++++- test/visits/reducers/tagVisits.test.ts | 2 +- 8 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/utils/helpers/date.ts b/src/utils/helpers/date.ts index 956e59d0..5f5a619f 100644 --- a/src/utils/helpers/date.ts +++ b/src/utils/helpers/date.ts @@ -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'; type DateOrString = Date | string; @@ -21,3 +21,25 @@ export const formatIsoDate = (date?: NullableDate) => formatDateFromFormat(date, export const formatInternational = formatDate(); 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 true; + } + + 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 false; +}; diff --git a/src/visits/reducers/orphanVisits.ts b/src/visits/reducers/orphanVisits.ts index 08d5320d..4a86c8e3 100644 --- a/src/visits/reducers/orphanVisits.ts +++ b/src/visits/reducers/orphanVisits.ts @@ -6,6 +6,7 @@ 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 } from './common'; 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 { visits: Visit[]; + query?: ShlinkVisitsParams; } type OrphanVisitsCombinedAction = OrphanVisitsAction @@ -39,13 +41,16 @@ const initialState: VisitsInfo = { export default buildReducer({ [GET_ORPHAN_VISITS_START]: () => ({ ...initialState, loading: true }), [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_CANCEL]: (state) => ({ ...state, cancelLoad: true }), [GET_ORPHAN_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }), [CREATE_VISITS]: (state, { createdVisits }) => { - const { visits } = state; - const newVisits = createdVisits.map(({ visit }) => visit); + const { visits, query = {} } = state; + const { startDate, endDate } = query; + const newVisits = createdVisits + .filter(({ visit }) => isBetween(visit.date, startDate, endDate)) + .map(({ visit }) => visit); return { ...state, visits: [ ...newVisits, ...visits ] }; }, @@ -66,6 +71,7 @@ export const getOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => return { ...result, data: visits }; }); const shouldCancel = () => getState().orphanVisits.cancelLoad; + const extraFinishActionData: Partial = { query }; const actionMap = { start: GET_ORPHAN_VISITS_START, large: GET_ORPHAN_VISITS_LARGE, @@ -74,7 +80,7 @@ export const getOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => 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); diff --git a/src/visits/reducers/shortUrlVisits.ts b/src/visits/reducers/shortUrlVisits.ts index 688b0812..81b26ca0 100644 --- a/src/visits/reducers/shortUrlVisits.ts +++ b/src/visits/reducers/shortUrlVisits.ts @@ -7,6 +7,7 @@ import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilde import { GetState } from '../../container/types'; import { ShlinkVisitsParams } from '../../api/types'; import { ApiErrorAction } from '../../api/types/actions'; +import { isBetween } from '../../utils/helpers/date'; import { getVisitsWithLoader } from './common'; import { CREATE_VISITS, CreateVisitsAction } from './visitCreation'; @@ -23,6 +24,7 @@ export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {} interface ShortUrlVisitsAction extends Action, ShortUrlIdentifier { visits: Visit[]; + query?: ShlinkVisitsParams; } type ShortUrlVisitsCombinedAction = ShortUrlVisitsAction @@ -33,7 +35,7 @@ type ShortUrlVisitsCombinedAction = ShortUrlVisitsAction const initialState: ShortUrlVisits = { visits: [], shortCode: '', - domain: undefined, + domain: undefined, // Deprecated. Value from query params can be used instead loading: false, loadingLarge: false, error: false, @@ -44,22 +46,27 @@ const initialState: ShortUrlVisits = { export default buildReducer({ [GET_SHORT_URL_VISITS_START]: () => ({ ...initialState, loading: true }), [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, visits, shortCode, domain, + query, }), [GET_SHORT_URL_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }), [GET_SHORT_URL_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }), [GET_SHORT_URL_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }), [CREATE_VISITS]: (state, { createdVisits }) => { - const { shortCode, domain, visits } = state; + const { shortCode, domain, visits, query = {} } = state; + const { startDate, endDate } = query; 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); - return { ...state, visits: [ ...newVisits, ...visits ] }; + return newVisits.length === 0 ? state : { ...state, visits: [ ...newVisits, ...visits ] }; }, }, initialState); @@ -73,7 +80,7 @@ export const getShortUrlVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) { ...query, page, itemsPerPage }, ); const shouldCancel = () => getState().shortUrlVisits.cancelLoad; - const extraFinishActionData: Partial = { shortCode, domain: query.domain }; + const extraFinishActionData: Partial = { shortCode, query, domain: query.domain }; const actionMap = { start: GET_SHORT_URL_VISITS_START, large: GET_SHORT_URL_VISITS_LARGE, diff --git a/src/visits/reducers/tagVisits.ts b/src/visits/reducers/tagVisits.ts index cc8140ae..389dd56c 100644 --- a/src/visits/reducers/tagVisits.ts +++ b/src/visits/reducers/tagVisits.ts @@ -7,6 +7,7 @@ import { ShlinkVisitsParams } from '../../api/types'; import { ApiErrorAction } from '../../api/types/actions'; import { getVisitsWithLoader } from './common'; import { CREATE_VISITS, CreateVisitsAction } from './visitCreation'; +import { isBetween } from '../../utils/helpers/date'; /* eslint-disable padding-line-between-statements */ export const GET_TAG_VISITS_START = 'shlink/tagVisits/GET_TAG_VISITS_START'; @@ -24,6 +25,7 @@ export interface TagVisits extends VisitsInfo { export interface TagVisitsAction extends Action { visits: Visit[]; tag: string; + query?: ShlinkVisitsParams; } type TagsVisitsCombinedAction = TagVisitsAction @@ -44,14 +46,15 @@ const initialState: TagVisits = { export default buildReducer({ [GET_TAG_VISITS_START]: () => ({ ...initialState, loading: true }), [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_CANCEL]: (state) => ({ ...state, cancelLoad: true }), [GET_TAG_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }), [CREATE_VISITS]: (state, { createdVisits }) => { - const { tag, visits } = state; + const { tag, visits, query = {} } = state; + const { startDate, endDate } = query; const newVisits = createdVisits - .filter(({ shortUrl }) => shortUrl?.tags.includes(tag)) + .filter(({ shortUrl, visit }) => shortUrl?.tags.includes(tag) && isBetween(visit.date, startDate, endDate)) .map(({ visit }) => visit); return { ...state, visits: [ ...newVisits, ...visits ] }; @@ -68,7 +71,7 @@ export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( { ...query, page, itemsPerPage }, ); const shouldCancel = () => getState().tagVisits.cancelLoad; - const extraFinishActionData: Partial = { tag }; + const extraFinishActionData: Partial = { tag, query }; const actionMap = { start: GET_TAG_VISITS_START, large: GET_TAG_VISITS_LARGE, diff --git a/src/visits/types/index.ts b/src/visits/types/index.ts index 2a4de853..05f8226b 100644 --- a/src/visits/types/index.ts +++ b/src/visits/types/index.ts @@ -1,6 +1,6 @@ import { Action } from 'redux'; import { ShortUrl } from '../../short-urls/data'; -import { ProblemDetailsError } from '../../api/types'; +import { ProblemDetailsError, ShlinkVisitsParams } from '../../api/types'; import { DateRange } from '../../utils/dates/types'; export interface VisitsInfo { @@ -11,6 +11,7 @@ export interface VisitsInfo { errorData?: ProblemDetailsError; progress: number; cancelLoad: boolean; + query?: ShlinkVisitsParams; } export interface VisitsLoadProgressChangedAction extends Action { diff --git a/test/visits/reducers/orphanVisits.test.ts b/test/visits/reducers/orphanVisits.test.ts index 75fe7253..7c6ad8a9 100644 --- a/test/visits/reducers/orphanVisits.test.ts +++ b/test/visits/reducers/orphanVisits.test.ts @@ -124,7 +124,7 @@ describe('orphanVisitsReducer', () => { expect(dispatchMock).toHaveBeenCalledTimes(2); 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); }); }); diff --git a/test/visits/reducers/shortUrlVisits.test.ts b/test/visits/reducers/shortUrlVisits.test.ts index 947faabd..147a9798 100644 --- a/test/visits/reducers/shortUrlVisits.test.ts +++ b/test/visits/reducers/shortUrlVisits.test.ts @@ -133,7 +133,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 }); + expect(dispatchMock).toHaveBeenNthCalledWith( + 2, + { type: GET_SHORT_URL_VISITS, visits, shortCode, domain, query: query ?? {} }, + ); expect(ShlinkApiClient.getShortUrlVisits).toHaveBeenCalledTimes(1); }); diff --git a/test/visits/reducers/tagVisits.test.ts b/test/visits/reducers/tagVisits.test.ts index dd595899..1e8f9797 100644 --- a/test/visits/reducers/tagVisits.test.ts +++ b/test/visits/reducers/tagVisits.test.ts @@ -132,7 +132,7 @@ describe('tagVisitsReducer', () => { expect(dispatchMock).toHaveBeenCalledTimes(2); 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); }); }); From 6d392ba403c8e7ffc95b0d0799492deabb22e6a7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Oct 2021 22:37:14 +0200 Subject: [PATCH 2/2] Added more tests covering how real-time visits are filtered out based on date intervals --- CHANGELOG.md | 2 +- src/utils/helpers/date.ts | 6 +-- src/visits/reducers/tagVisits.ts | 2 +- test/visits/reducers/orphanVisits.test.ts | 44 ++++++++++++++-- test/visits/reducers/shortUrlVisits.test.ts | 58 +++++++++++++++++++-- test/visits/reducers/tagVisits.test.ts | 58 +++++++++++++++++++-- 6 files changed, 151 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55c3f84f..55f61405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * *Nothing* ### 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 diff --git a/src/utils/helpers/date.ts b/src/utils/helpers/date.ts index 5f5a619f..5ce27ae3 100644 --- a/src/utils/helpers/date.ts +++ b/src/utils/helpers/date.ts @@ -25,10 +25,6 @@ export const parseDate = (date: string, format: string) => parse(date, format, n const parseISO = (date: DateOrString): Date => isDateObject(date) ? date : stdParseISO(date); export const isBetween = (date: DateOrString, start?: DateOrString, end?: DateOrString): boolean => { - if (!start && !end) { - return true; - } - if (!start && end) { return isBefore(parseISO(date), parseISO(end)); } @@ -41,5 +37,5 @@ export const isBetween = (date: DateOrString, start?: DateOrString, end?: DateOr return isWithinInterval(parseISO(date), { start: parseISO(start), end: parseISO(end) }); } - return false; + return true; }; diff --git a/src/visits/reducers/tagVisits.ts b/src/visits/reducers/tagVisits.ts index 389dd56c..c628021f 100644 --- a/src/visits/reducers/tagVisits.ts +++ b/src/visits/reducers/tagVisits.ts @@ -5,9 +5,9 @@ import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilde import { GetState } from '../../container/types'; import { ShlinkVisitsParams } from '../../api/types'; import { ApiErrorAction } from '../../api/types/actions'; +import { isBetween } from '../../utils/helpers/date'; import { getVisitsWithLoader } from './common'; import { CREATE_VISITS, CreateVisitsAction } from './visitCreation'; -import { isBetween } from '../../utils/helpers/date'; /* eslint-disable padding-line-between-statements */ export const GET_TAG_VISITS_START = 'shlink/tagVisits/GET_TAG_VISITS_START'; diff --git a/test/visits/reducers/orphanVisits.test.ts b/test/visits/reducers/orphanVisits.test.ts index 7c6ad8a9..86296131 100644 --- a/test/visits/reducers/orphanVisits.test.ts +++ b/test/visits/reducers/orphanVisits.test.ts @@ -1,4 +1,5 @@ import { Mock } from 'ts-mockery'; +import { addDays, subDays } from 'date-fns'; import reducer, { getOrphanVisits, cancelGetOrphanVisits, @@ -15,8 +16,10 @@ import { Visit, VisitsInfo } from '../../../src/visits/types'; import { ShlinkVisits } from '../../../src/api/types'; import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient'; import { ShlinkState } from '../../../src/container/types'; +import { formatIsoDate } from '../../../src/utils/helpers/date'; describe('orphanVisitsReducer', () => { + const now = new Date(); const visitsMocks = rangeOf(2, () => Mock.all()); describe('reducer', () => { @@ -64,15 +67,48 @@ describe('orphanVisitsReducer', () => { expect(visits).toEqual(actionVisits); }); - it('prepends new visits on CREATE_VISIT', () => { - const prevState = buildState({ visits: visitsMocks }); + it.each([ + [{}, visitsMocks.length + 2 ], + [ + Mock.of({ + query: { endDate: formatIsoDate(subDays(now, 1)) ?? undefined }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + query: { startDate: formatIsoDate(addDays(now, 1)) ?? undefined }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + query: { + startDate: formatIsoDate(subDays(now, 5)) ?? undefined, + endDate: formatIsoDate(subDays(now, 2)) ?? undefined, + }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + 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({ date: formatIsoDate(now) ?? undefined }); const { visits } = reducer( 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', () => { diff --git a/test/visits/reducers/shortUrlVisits.test.ts b/test/visits/reducers/shortUrlVisits.test.ts index 147a9798..8d45f7da 100644 --- a/test/visits/reducers/shortUrlVisits.test.ts +++ b/test/visits/reducers/shortUrlVisits.test.ts @@ -1,4 +1,5 @@ import { Mock } from 'ts-mockery'; +import { addDays, subDays } from 'date-fns'; import reducer, { getShortUrlVisits, cancelGetShortUrlVisits, @@ -16,8 +17,10 @@ import { Visit } from '../../../src/visits/types'; import { ShlinkVisits } from '../../../src/api/types'; import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient'; import { ShlinkState } from '../../../src/container/types'; +import { formatIsoDate } from '../../../src/utils/helpers/date'; describe('shortUrlVisitsReducer', () => { + const now = new Date(); const visitsMocks = rangeOf(2, () => Mock.all()); describe('reducer', () => { @@ -66,8 +69,52 @@ describe('shortUrlVisitsReducer', () => { }); it.each([ - [{ shortCode: 'abc123' }, [{}, ...visitsMocks ]], - [{ shortCode: 'def456' }, visitsMocks ], + [{ shortCode: 'abc123' }, visitsMocks.length + 1 ], + [{ shortCode: 'def456' }, visitsMocks.length ], + [ + Mock.of({ + shortCode: 'abc123', + query: { endDate: formatIsoDate(subDays(now, 1)) ?? undefined }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + shortCode: 'abc123', + query: { startDate: formatIsoDate(addDays(now, 1)) ?? undefined }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + shortCode: 'abc123', + query: { + startDate: formatIsoDate(subDays(now, 5)) ?? undefined, + endDate: formatIsoDate(subDays(now, 2)) ?? undefined, + }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + shortCode: 'abc123', + query: { + startDate: formatIsoDate(subDays(now, 5)) ?? undefined, + endDate: formatIsoDate(addDays(now, 3)) ?? undefined, + }, + }), + visitsMocks.length + 1, + ], + [ + Mock.of({ + 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) => { const shortUrl = { shortCode: 'abc123', @@ -77,9 +124,12 @@ describe('shortUrlVisitsReducer', () => { 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', () => { diff --git a/test/visits/reducers/tagVisits.test.ts b/test/visits/reducers/tagVisits.test.ts index 1e8f9797..f026e5bb 100644 --- a/test/visits/reducers/tagVisits.test.ts +++ b/test/visits/reducers/tagVisits.test.ts @@ -1,4 +1,5 @@ import { Mock } from 'ts-mockery'; +import { addDays, subDays } from 'date-fns'; import reducer, { getTagVisits, cancelGetTagVisits, @@ -16,8 +17,10 @@ import { Visit } from '../../../src/visits/types'; import { ShlinkVisits } from '../../../src/api/types'; import ShlinkApiClient from '../../../src/api/services/ShlinkApiClient'; import { ShlinkState } from '../../../src/container/types'; +import { formatIsoDate } from '../../../src/utils/helpers/date'; describe('tagVisitsReducer', () => { + const now = new Date(); const visitsMocks = rangeOf(2, () => Mock.all()); describe('reducer', () => { @@ -66,8 +69,52 @@ describe('tagVisitsReducer', () => { }); it.each([ - [{ tag: 'foo' }, [{}, ...visitsMocks ]], - [{ tag: 'bar' }, visitsMocks ], + [{ tag: 'foo' }, visitsMocks.length + 1 ], + [{ tag: 'bar' }, visitsMocks.length ], + [ + Mock.of({ + tag: 'foo', + query: { endDate: formatIsoDate(subDays(now, 1)) ?? undefined }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + tag: 'foo', + query: { startDate: formatIsoDate(addDays(now, 1)) ?? undefined }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + tag: 'foo', + query: { + startDate: formatIsoDate(subDays(now, 5)) ?? undefined, + endDate: formatIsoDate(subDays(now, 2)) ?? undefined, + }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + tag: 'foo', + query: { + startDate: formatIsoDate(subDays(now, 5)) ?? undefined, + endDate: formatIsoDate(addDays(now, 3)) ?? undefined, + }, + }), + visitsMocks.length + 1, + ], + [ + Mock.of({ + 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) => { const shortUrl = { tags: [ 'foo', 'baz' ], @@ -77,9 +124,12 @@ describe('tagVisitsReducer', () => { 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', () => {