From 3aa990a1b09a06fc7018f17b1c7423881c71ef45 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 30 Apr 2022 09:11:30 +0200 Subject: [PATCH] Created domainVisits reducer test --- test/visits/reducers/domainVisits.test.ts | 235 ++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 test/visits/reducers/domainVisits.test.ts diff --git a/test/visits/reducers/domainVisits.test.ts b/test/visits/reducers/domainVisits.test.ts new file mode 100644 index 00000000..9470777e --- /dev/null +++ b/test/visits/reducers/domainVisits.test.ts @@ -0,0 +1,235 @@ +import { Mock } from 'ts-mockery'; +import { addDays, formatISO, subDays } from 'date-fns'; +import reducer, { + getDomainVisits, + cancelGetDomainVisits, + GET_DOMAIN_VISITS_START, + GET_DOMAIN_VISITS_ERROR, + GET_DOMAIN_VISITS, + GET_DOMAIN_VISITS_LARGE, + GET_DOMAIN_VISITS_CANCEL, + GET_DOMAIN_VISITS_PROGRESS_CHANGED, + GET_DOMAIN_VISITS_FALLBACK_TO_INTERVAL, + DomainVisits, +} from '../../../src/visits/reducers/domainVisits'; +import { CREATE_VISITS } from '../../../src/visits/reducers/visitCreation'; +import { rangeOf } from '../../../src/utils/utils'; +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'; +import { DateInterval } from '../../../src/utils/dates/types'; + +describe('domainVisitsReducer', () => { + const now = new Date(); + const visitsMocks = rangeOf(2, () => Mock.all()); + + describe('reducer', () => { + const buildState = (data: Partial) => Mock.of(data); + + it('returns loading on GET_DOMAIN_VISITS_START', () => { + const state = reducer(buildState({ loading: false }), { type: GET_DOMAIN_VISITS_START } as any); + const { loading } = state; + + expect(loading).toEqual(true); + }); + + it('returns loadingLarge on GET_DOMAIN_VISITS_LARGE', () => { + const state = reducer(buildState({ loadingLarge: false }), { type: GET_DOMAIN_VISITS_LARGE } as any); + const { loadingLarge } = state; + + expect(loadingLarge).toEqual(true); + }); + + it('returns cancelLoad on GET_DOMAIN_VISITS_CANCEL', () => { + const state = reducer(buildState({ cancelLoad: false }), { type: GET_DOMAIN_VISITS_CANCEL } as any); + const { cancelLoad } = state; + + expect(cancelLoad).toEqual(true); + }); + + it('stops loading and returns error on GET_DOMAIN_VISITS_ERROR', () => { + const state = reducer(buildState({ loading: true, error: false }), { type: GET_DOMAIN_VISITS_ERROR } as any); + const { loading, error } = state; + + expect(loading).toEqual(false); + expect(error).toEqual(true); + }); + + 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 { loading, error, visits } = state; + + expect(loading).toEqual(false); + expect(error).toEqual(false); + expect(visits).toEqual(actionVisits); + }); + + it.each([ + [{ domain: 'foo.com' }, visitsMocks.length + 1], + [{ domain: 'bar.com' }, visitsMocks.length], + [ + Mock.of({ + domain: 'foo.com', + query: { endDate: formatIsoDate(subDays(now, 1)) ?? undefined }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + domain: 'foo.com', + query: { startDate: formatIsoDate(addDays(now, 1)) ?? undefined }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + domain: 'foo.com', + query: { + startDate: formatIsoDate(subDays(now, 5)) ?? undefined, + endDate: formatIsoDate(subDays(now, 2)) ?? undefined, + }, + }), + visitsMocks.length, + ], + [ + Mock.of({ + domain: 'foo.com', + query: { + startDate: formatIsoDate(subDays(now, 5)) ?? undefined, + endDate: formatIsoDate(addDays(now, 3)) ?? undefined, + }, + }), + visitsMocks.length + 1, + ], + [ + Mock.of({ + domain: 'bar.com', + 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 = { + domain: 'foo.com', + }; + const prevState = buildState({ + ...state, + visits: visitsMocks, + }); + + const { visits } = reducer(prevState, { + type: CREATE_VISITS, + createdVisits: [{ shortUrl, visit: { date: formatIsoDate(now) ?? undefined } }], + } as any); + + expect(visits).toHaveLength(expectedVisits); + }); + + it('returns new progress on GET_DOMAIN_VISITS_PROGRESS_CHANGED', () => { + const state = reducer(undefined, { type: GET_DOMAIN_VISITS_PROGRESS_CHANGED, progress: 85 } as any); + + expect(state).toEqual(expect.objectContaining({ progress: 85 })); + }); + + it('returns fallbackInterval on GET_DOMAIN_VISITS_FALLBACK_TO_INTERVAL', () => { + const fallbackInterval: DateInterval = 'last30Days'; + const state = reducer(undefined, { type: GET_DOMAIN_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval } as any); + + expect(state).toEqual(expect.objectContaining({ fallbackInterval })); + }); + }); + + describe('getDomainVisits', () => { + type GetVisitsReturn = Promise | ((shortCode: string, query: any) => Promise); + + const buildApiClientMock = (returned: GetVisitsReturn) => Mock.of({ + getDomainVisits: jest.fn(typeof returned === 'function' ? returned : async () => returned), + }); + const dispatchMock = jest.fn(); + const getState = () => Mock.of({ + domainVisits: { cancelLoad: false }, + }); + const domain = 'foo.com'; + + beforeEach(jest.clearAllMocks); + + it('dispatches start and error when promise is rejected', async () => { + const shlinkApiClient = buildApiClientMock(Promise.reject(new Error())); + + await getDomainVisits(() => shlinkApiClient)('foo.com')(dispatchMock, getState); + + expect(dispatchMock).toHaveBeenCalledTimes(2); + expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_DOMAIN_VISITS_START }); + expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_DOMAIN_VISITS_ERROR }); + expect(shlinkApiClient.getDomainVisits).toHaveBeenCalledTimes(1); + }); + + it.each([ + [undefined], + [{}], + ])('dispatches start and success when promise is resolved', async (query) => { + const visits = visitsMocks; + const shlinkApiClient = buildApiClientMock(Promise.resolve({ + data: visitsMocks, + pagination: { + currentPage: 1, + pagesCount: 1, + totalItems: 1, + }, + })); + + await getDomainVisits(() => shlinkApiClient)(domain, query)(dispatchMock, getState); + + 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(shlinkApiClient.getDomainVisits).toHaveBeenCalledTimes(1); + }); + + it.each([ + [ + [Mock.of({ date: formatISO(subDays(new Date(), 20)) })], + { type: GET_DOMAIN_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval: 'last30Days' }, + ], + [ + [Mock.of({ date: formatISO(subDays(new Date(), 100)) })], + { type: GET_DOMAIN_VISITS_FALLBACK_TO_INTERVAL, fallbackInterval: 'last180Days' }, + ], + [[], expect.objectContaining({ type: GET_DOMAIN_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 getShlinkDomainVisits = jest.fn() + .mockResolvedValueOnce(buildVisitsResult()) + .mockResolvedValueOnce(buildVisitsResult(lastVisits)); + const ShlinkApiClient = Mock.of({ getDomainVisits: getShlinkDomainVisits }); + + await getDomainVisits(() => ShlinkApiClient)(domain, {}, true)(dispatchMock, getState); + + expect(dispatchMock).toHaveBeenCalledTimes(2); + expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_DOMAIN_VISITS_START }); + expect(dispatchMock).toHaveBeenNthCalledWith(2, expectedSecondDispatch); + expect(getShlinkDomainVisits).toHaveBeenCalledTimes(2); + }); + }); + + describe('cancelGetDomainVisits', () => { + it('just returns the action with proper type', () => + expect(cancelGetDomainVisits()).toEqual({ type: GET_DOMAIN_VISITS_CANCEL })); + }); +});