Added logic in reducers to fallback to a different date interval if default one returns no visits

This commit is contained in:
Alejandro Celaya
2021-12-23 10:38:02 +01:00
parent a30687e4ea
commit e22856ff74
7 changed files with 193 additions and 21 deletions

View File

@@ -1,5 +1,5 @@
import { Mock } from 'ts-mockery';
import { addDays, subDays } from 'date-fns';
import { addDays, formatISO, subDays } from 'date-fns';
import reducer, {
getOrphanVisits,
cancelGetOrphanVisits,
@@ -9,6 +9,7 @@ import reducer, {
GET_ORPHAN_VISITS_LARGE,
GET_ORPHAN_VISITS_CANCEL,
GET_ORPHAN_VISITS_PROGRESS_CHANGED,
GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL,
} from '../../../src/visits/reducers/orphanVisits';
import { CREATE_VISITS } from '../../../src/visits/reducers/visitCreation';
import { rangeOf } from '../../../src/utils/utils';
@@ -17,6 +18,7 @@ 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';
import { DateInterval } from '../../../src/utils/dates/types';
describe('orphanVisitsReducer', () => {
const now = new Date();
@@ -116,6 +118,13 @@ describe('orphanVisitsReducer', () => {
expect(state).toEqual(expect.objectContaining({ progress: 85 }));
});
it('returns fallbackInterval on GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL', () => {
const fallbackInterval: DateInterval = 'last30Days';
const state = reducer(undefined, { type: GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval } as any);
expect(state).toEqual(expect.objectContaining({ fallbackInterval }));
});
});
describe('getOrphanVisits', () => {
@@ -163,6 +172,38 @@ describe('orphanVisitsReducer', () => {
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_ORPHAN_VISITS, visits, query: query ?? {} });
expect(ShlinkApiClient.getOrphanVisits).toHaveBeenCalledTimes(1);
});
it.each([
[
[ Mock.of<Visit>({ date: formatISO(subDays(new Date(), 5)) }) ],
{ type: GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval: 'last7Days' },
],
[
[ Mock.of<Visit>({ date: formatISO(subDays(new Date(), 200)) }) ],
{ type: GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval: 'last365Days' },
],
[[], expect.objectContaining({ type: GET_ORPHAN_VISITS }) ],
])('dispatches fallback interval when the list of visits is empty', async (lastVisits, expectedSecondDispatch) => {
const buildVisitsResult = (data: Visit[] = []): ShlinkVisits => ({
data,
pagination: {
currentPage: 1,
pagesCount: 1,
totalItems: 1,
},
});
const getShlinkOrphanVisits = jest.fn()
.mockResolvedValueOnce(buildVisitsResult())
.mockResolvedValueOnce(buildVisitsResult(lastVisits));
const ShlinkApiClient = Mock.of<ShlinkApiClient>({ getOrphanVisits: getShlinkOrphanVisits });
await getOrphanVisits(() => ShlinkApiClient)({}, undefined, true)(dispatchMock, getState);
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_ORPHAN_VISITS_START });
expect(dispatchMock).toHaveBeenNthCalledWith(2, expectedSecondDispatch);
expect(getShlinkOrphanVisits).toHaveBeenCalledTimes(2);
});
});
describe('cancelGetOrphanVisits', () => {

View File

@@ -1,5 +1,5 @@
import { Mock } from 'ts-mockery';
import { addDays, subDays } from 'date-fns';
import { addDays, formatISO, subDays } from 'date-fns';
import reducer, {
getShortUrlVisits,
cancelGetShortUrlVisits,
@@ -9,6 +9,7 @@ import reducer, {
GET_SHORT_URL_VISITS_LARGE,
GET_SHORT_URL_VISITS_CANCEL,
GET_SHORT_URL_VISITS_PROGRESS_CHANGED,
GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL,
ShortUrlVisits,
} from '../../../src/visits/reducers/shortUrlVisits';
import { CREATE_VISITS } from '../../../src/visits/reducers/visitCreation';
@@ -18,6 +19,7 @@ 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';
import { DateInterval } from '../../../src/utils/dates/types';
describe('shortUrlVisitsReducer', () => {
const now = new Date();
@@ -137,6 +139,13 @@ describe('shortUrlVisitsReducer', () => {
expect(state).toEqual(expect.objectContaining({ progress: 85 }));
});
it('returns fallbackInterval on GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL', () => {
const fallbackInterval: DateInterval = 'last30Days';
const state = reducer(undefined, { type: GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval } as any);
expect(state).toEqual(expect.objectContaining({ fallbackInterval }));
});
});
describe('getShortUrlVisits', () => {
@@ -209,6 +218,38 @@ describe('shortUrlVisitsReducer', () => {
visits: [ ...visitsMocks, ...visitsMocks, ...visitsMocks ],
}));
});
it.each([
[
[ Mock.of<Visit>({ date: formatISO(subDays(new Date(), 5)) }) ],
{ type: GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval: 'last7Days' },
],
[
[ Mock.of<Visit>({ date: formatISO(subDays(new Date(), 200)) }) ],
{ type: GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval: 'last365Days' },
],
[[], expect.objectContaining({ type: GET_SHORT_URL_VISITS }) ],
])('dispatches fallback interval when the list of visits is empty', async (lastVisits, expectedSecondDispatch) => {
const buildVisitsResult = (data: Visit[] = []): ShlinkVisits => ({
data,
pagination: {
currentPage: 1,
pagesCount: 1,
totalItems: 1,
},
});
const getShlinkShortUrlVisits = jest.fn()
.mockResolvedValueOnce(buildVisitsResult())
.mockResolvedValueOnce(buildVisitsResult(lastVisits));
const ShlinkApiClient = Mock.of<ShlinkApiClient>({ getShortUrlVisits: getShlinkShortUrlVisits });
await getShortUrlVisits(() => ShlinkApiClient)('abc123', {}, true)(dispatchMock, getState);
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_SHORT_URL_VISITS_START });
expect(dispatchMock).toHaveBeenNthCalledWith(2, expectedSecondDispatch);
expect(getShlinkShortUrlVisits).toHaveBeenCalledTimes(2);
});
});
describe('cancelGetShortUrlVisits', () => {

View File

@@ -1,5 +1,5 @@
import { Mock } from 'ts-mockery';
import { addDays, subDays } from 'date-fns';
import { addDays, formatISO, subDays } from 'date-fns';
import reducer, {
getTagVisits,
cancelGetTagVisits,
@@ -9,6 +9,7 @@ import reducer, {
GET_TAG_VISITS_LARGE,
GET_TAG_VISITS_CANCEL,
GET_TAG_VISITS_PROGRESS_CHANGED,
GET_TAG_VISITS_FALLBACK_TO_INTERVAL,
TagVisits,
} from '../../../src/visits/reducers/tagVisits';
import { CREATE_VISITS } from '../../../src/visits/reducers/visitCreation';
@@ -18,6 +19,7 @@ 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';
import { DateInterval } from '../../../src/utils/dates/types';
describe('tagVisitsReducer', () => {
const now = new Date();
@@ -137,6 +139,13 @@ describe('tagVisitsReducer', () => {
expect(state).toEqual(expect.objectContaining({ progress: 85 }));
});
it('returns fallbackInterval on GET_TAG_VISITS_FALLBACK_TO_INTERVAL', () => {
const fallbackInterval: DateInterval = 'last30Days';
const state = reducer(undefined, { type: GET_TAG_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval } as any);
expect(state).toEqual(expect.objectContaining({ fallbackInterval }));
});
});
describe('getTagVisits', () => {
@@ -149,8 +158,9 @@ describe('tagVisitsReducer', () => {
const getState = () => Mock.of<ShlinkState>({
tagVisits: { cancelLoad: false },
});
const tag = 'foo';
beforeEach(jest.resetAllMocks);
beforeEach(jest.clearAllMocks);
it('dispatches start and error when promise is rejected', async () => {
const ShlinkApiClient = buildApiClientMock(Promise.reject({}));
@@ -168,7 +178,6 @@ describe('tagVisitsReducer', () => {
[{}],
])('dispatches start and success when promise is resolved', async (query) => {
const visits = visitsMocks;
const tag = 'foo';
const ShlinkApiClient = buildApiClientMock(Promise.resolve({
data: visitsMocks,
pagination: {
@@ -185,6 +194,38 @@ describe('tagVisitsReducer', () => {
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_TAG_VISITS, visits, tag, query: query ?? {} });
expect(ShlinkApiClient.getTagVisits).toHaveBeenCalledTimes(1);
});
it.each([
[
[ Mock.of<Visit>({ date: formatISO(subDays(new Date(), 20)) }) ],
{ type: GET_TAG_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval: 'last30Days' },
],
[
[ Mock.of<Visit>({ date: formatISO(subDays(new Date(), 100)) }) ],
{ type: GET_TAG_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval: 'last180Days' },
],
[[], expect.objectContaining({ type: GET_TAG_VISITS }) ],
])('dispatches fallback interval when the list of visits is empty', async (lastVisits, expectedSecondDispatch) => {
const buildVisitsResult = (data: Visit[] = []): ShlinkVisits => ({
data,
pagination: {
currentPage: 1,
pagesCount: 1,
totalItems: 1,
},
});
const getShlinkTagVisits = jest.fn()
.mockResolvedValueOnce(buildVisitsResult())
.mockResolvedValueOnce(buildVisitsResult(lastVisits));
const ShlinkApiClient = Mock.of<ShlinkApiClient>({ getTagVisits: getShlinkTagVisits });
await getTagVisits(() => ShlinkApiClient)(tag, {}, true)(dispatchMock, getState);
expect(dispatchMock).toHaveBeenCalledTimes(2);
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });
expect(dispatchMock).toHaveBeenNthCalledWith(2, expectedSecondDispatch);
expect(getShlinkTagVisits).toHaveBeenCalledTimes(2);
});
});
describe('cancelGetTagVisits', () => {