From 7bfccafca88c6efced94a302e732ad509a2962d2 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 9 Nov 2022 19:13:44 +0100 Subject: [PATCH] Migrated shortUrlsList reducer to RTK --- src/reducers/index.ts | 3 +- src/short-urls/reducers/shortUrlCreation.ts | 3 +- src/short-urls/reducers/shortUrlDeletion.ts | 34 ++-- src/short-urls/reducers/shortUrlEdition.ts | 5 +- src/short-urls/reducers/shortUrlsList.ts | 191 +++++++++--------- src/short-urls/services/provideServices.ts | 21 +- src/tags/reducers/tagsList.ts | 4 +- src/tags/services/provideServices.ts | 5 +- .../reducers/shortUrlDeletion.test.ts | 8 +- .../reducers/shortUrlEdition.test.ts | 2 +- .../short-urls/reducers/shortUrlsList.test.ts | 64 +++--- test/tags/reducers/tagsList.test.ts | 4 +- 12 files changed, 184 insertions(+), 160 deletions(-) diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 2053ebc2..66ff189c 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -2,7 +2,6 @@ import { IContainer } from 'bottlejs'; import { combineReducers } from 'redux'; import { serversReducer } from '../servers/reducers/servers'; import selectedServerReducer from '../servers/reducers/selectedServer'; -import shortUrlsListReducer from '../short-urls/reducers/shortUrlsList'; import shortUrlVisitsReducer from '../visits/reducers/shortUrlVisits'; import tagVisitsReducer from '../visits/reducers/tagVisits'; import domainVisitsReducer from '../visits/reducers/domainVisits'; @@ -17,7 +16,7 @@ import { ShlinkState } from '../container/types'; export default (container: IContainer) => combineReducers({ servers: serversReducer, selectedServer: selectedServerReducer, - shortUrlsList: shortUrlsListReducer, + shortUrlsList: container.shortUrlsListReducer, shortUrlCreation: container.shortUrlCreationReducer, shortUrlDeletion: container.shortUrlDeletionReducer, shortUrlEdition: container.shortUrlEditionReducer, diff --git a/src/short-urls/reducers/shortUrlCreation.ts b/src/short-urls/reducers/shortUrlCreation.ts index f1dbd725..1a48b63e 100644 --- a/src/short-urls/reducers/shortUrlCreation.ts +++ b/src/short-urls/reducers/shortUrlCreation.ts @@ -6,7 +6,6 @@ import { parseApiError } from '../../api/utils'; import { ProblemDetailsError } from '../../api/types/errors'; const REDUCER_PREFIX = 'shlink/shortUrlCreation'; -export const CREATE_SHORT_URL = `${REDUCER_PREFIX}/createShortUrl`; export type ShortUrlCreation = { saving: false; @@ -37,7 +36,7 @@ const initialState: ShortUrlCreation = { }; export const createShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk( - CREATE_SHORT_URL, + `${REDUCER_PREFIX}/createShortUrl`, (data: ShortUrlData, { getState }): Promise => { const { createShortUrl: shlinkCreateShortUrl } = buildShlinkApiClient(getState); return shlinkCreateShortUrl(data); diff --git a/src/short-urls/reducers/shortUrlDeletion.ts b/src/short-urls/reducers/shortUrlDeletion.ts index bfa9406c..67d86499 100644 --- a/src/short-urls/reducers/shortUrlDeletion.ts +++ b/src/short-urls/reducers/shortUrlDeletion.ts @@ -1,4 +1,4 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; import { createAsyncThunk } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import { parseApiError } from '../../api/utils'; @@ -6,7 +6,6 @@ import { ProblemDetailsError } from '../../api/types/errors'; import { ShortUrlIdentifier } from '../data'; const REDUCER_PREFIX = 'shlink/shortUrlDeletion'; -export const SHORT_URL_DELETED = `${REDUCER_PREFIX}/deleteShortUrl`; export interface ShortUrlDeletion { shortCode: string; @@ -16,8 +15,6 @@ export interface ShortUrlDeletion { errorData?: ProblemDetailsError; } -export type DeleteShortUrlAction = PayloadAction; - const initialState: ShortUrlDeletion = { shortCode: '', loading: false, @@ -25,16 +22,16 @@ const initialState: ShortUrlDeletion = { error: false, }; -export const shortUrlDeletionReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => { - const deleteShortUrl = createAsyncThunk( - SHORT_URL_DELETED, - async ({ shortCode, domain }: ShortUrlIdentifier, { getState }): Promise => { - const { deleteShortUrl: shlinkDeleteShortUrl } = buildShlinkApiClient(getState); - await shlinkDeleteShortUrl(shortCode, domain); - return { shortCode, domain }; - }, - ); +export const deleteShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk( + `${REDUCER_PREFIX}/deleteShortUrl`, + async ({ shortCode, domain }: ShortUrlIdentifier, { getState }): Promise => { + const { deleteShortUrl: shlinkDeleteShortUrl } = buildShlinkApiClient(getState); + await shlinkDeleteShortUrl(shortCode, domain); + return { shortCode, domain }; + }, +); +export const shortUrlDeletionReducerCreator = (deleteShortUrlThunk: ReturnType) => { const { actions, reducer } = createSlice({ name: REDUCER_PREFIX, initialState, @@ -42,11 +39,14 @@ export const shortUrlDeletionReducerCreator = (buildShlinkApiClient: ShlinkApiCl resetDeleteShortUrl: () => initialState, }, extraReducers: (builder) => { - builder.addCase(deleteShortUrl.pending, (state) => ({ ...state, loading: true, error: false, deleted: false })); - builder.addCase(deleteShortUrl.rejected, (state, { error }) => ( + builder.addCase( + deleteShortUrlThunk.pending, + (state) => ({ ...state, loading: true, error: false, deleted: false }), + ); + builder.addCase(deleteShortUrlThunk.rejected, (state, { error }) => ( { ...state, errorData: parseApiError(error), loading: false, error: true, deleted: false } )); - builder.addCase(deleteShortUrl.fulfilled, (state, { payload }) => ( + builder.addCase(deleteShortUrlThunk.fulfilled, (state, { payload }) => ( { ...state, shortCode: payload.shortCode, loading: false, error: false, deleted: true } )); }, @@ -54,5 +54,5 @@ export const shortUrlDeletionReducerCreator = (buildShlinkApiClient: ShlinkApiCl const { resetDeleteShortUrl } = actions; - return { reducer, deleteShortUrl, resetDeleteShortUrl }; + return { reducer, resetDeleteShortUrl }; }; diff --git a/src/short-urls/reducers/shortUrlEdition.ts b/src/short-urls/reducers/shortUrlEdition.ts index 6b9d5342..79ea145d 100644 --- a/src/short-urls/reducers/shortUrlEdition.ts +++ b/src/short-urls/reducers/shortUrlEdition.ts @@ -6,7 +6,6 @@ import { parseApiError } from '../../api/utils'; import { ProblemDetailsError } from '../../api/types/errors'; const REDUCER_PREFIX = 'shlink/shortUrlEdition'; -export const SHORT_URL_EDITED = `${REDUCER_PREFIX}/editShortUrl`; export interface ShortUrlEdition { shortUrl?: ShortUrl; @@ -29,7 +28,7 @@ const initialState: ShortUrlEdition = { }; export const editShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk( - SHORT_URL_EDITED, + `${REDUCER_PREFIX}/editShortUrl`, ({ shortCode, domain, data }: EditShortUrl, { getState }): Promise => { const { updateShortUrl } = buildShlinkApiClient(getState); return updateShortUrl(shortCode, domain, data as any); // FIXME parse dates @@ -51,4 +50,4 @@ export const shortUrlEditionReducerCreator = (editShortUrlThunk: ReturnType ({ shortUrl, saving: false, error: false, saved: true }), ); }, -}).reducer; +}); diff --git a/src/short-urls/reducers/shortUrlsList.ts b/src/short-urls/reducers/shortUrlsList.ts index 4cc56a07..c6885353 100644 --- a/src/short-urls/reducers/shortUrlsList.ts +++ b/src/short-urls/reducers/shortUrlsList.ts @@ -1,21 +1,16 @@ -import { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; import { assoc, assocPath, last, pipe, reject } from 'ramda'; -import { Dispatch } from 'redux'; import { shortUrlMatches } from '../helpers'; -import { createNewVisits, CreateVisitsAction } from '../../visits/reducers/visitCreation'; -import { buildReducer } from '../../utils/helpers/redux'; -import { GetState } from '../../container/types'; +import { createNewVisits } from '../../visits/reducers/visitCreation'; +import { createAsyncThunk } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import { ShlinkShortUrlsListParams, ShlinkShortUrlsResponse } from '../../api/types'; -import { DeleteShortUrlAction, SHORT_URL_DELETED } from './shortUrlDeletion'; -import { CREATE_SHORT_URL, CreateShortUrlAction } from './shortUrlCreation'; -import { SHORT_URL_EDITED, ShortUrlEditedAction } from './shortUrlEdition'; +import { deleteShortUrl } from './shortUrlDeletion'; +import { createShortUrl } from './shortUrlCreation'; +import { editShortUrl } from './shortUrlEdition'; import { ShortUrl } from '../data'; -export const LIST_SHORT_URLS_START = 'shlink/shortUrlsList/LIST_SHORT_URLS_START'; -export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR'; -export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; - +const REDUCER_PREFIX = 'shlink/shortUrlsList'; export const ITEMS_IN_OVERVIEW_PAGE = 5; export interface ShortUrlsList { @@ -24,94 +19,104 @@ export interface ShortUrlsList { error: boolean; } -export type ListShortUrlsAction = PayloadAction; - -export type ListShortUrlsCombinedAction = ( - ListShortUrlsAction - & CreateVisitsAction - & CreateShortUrlAction - & DeleteShortUrlAction - & ShortUrlEditedAction -); - const initialState: ShortUrlsList = { loading: true, error: false, }; -export default buildReducer({ - [LIST_SHORT_URLS_START]: (state) => ({ ...state, loading: true, error: false }), - [LIST_SHORT_URLS_ERROR]: () => ({ loading: false, error: true }), - [LIST_SHORT_URLS]: (_, { payload: shortUrls }) => ({ loading: false, error: false, shortUrls }), - [`${SHORT_URL_DELETED}/fulfilled`]: pipe( // TODO Do not hardcode action type here - (state: ShortUrlsList, { payload }: DeleteShortUrlAction) => (!state.shortUrls ? state : assocPath( - ['shortUrls', 'data'], - reject((shortUrl) => - shortUrlMatches(shortUrl, payload.shortCode, payload.domain), state.shortUrls.data), - state, - )), - (state) => (!state.shortUrls ? state : assocPath( - ['shortUrls', 'pagination', 'totalItems'], - state.shortUrls.pagination.totalItems - 1, - state, - )), - ), - [createNewVisits.toString()]: (state, { payload }) => assocPath( - ['shortUrls', 'data'], - state.shortUrls?.data?.map( - (currentShortUrl) => { - // Find the last of the new visit for this short URL, and pick the amount of visits from it - const lastVisit = last( - payload.createdVisits.filter( - ({ shortUrl }) => shortUrl && shortUrlMatches(currentShortUrl, shortUrl.shortCode, shortUrl.domain), - ), - ); +export const listShortUrls = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk( + `${REDUCER_PREFIX}/listShortUrls`, + (params: ShlinkShortUrlsListParams | void, { getState }): Promise => { + const { listShortUrls: shlinkListShortUrls } = buildShlinkApiClient(getState); + return shlinkListShortUrls(params ?? {}); + }, +); - return lastVisit?.shortUrl - ? assoc('visitsCount', lastVisit.shortUrl.visitsCount, currentShortUrl) - : currentShortUrl; - }, - ), - state, - ), - [`${CREATE_SHORT_URL}/fulfilled`]: pipe( // TODO Do not hardcode action type here - // The only place where the list and the creation form coexist is the overview page. - // There we can assume we are displaying page 1, and therefore, we can safely prepend the new short URL. - // We can also remove the items above the amount that is displayed there. - (state: ShortUrlsList, { payload }: CreateShortUrlAction) => (!state.shortUrls ? state : assocPath( - ['shortUrls', 'data'], - [payload, ...state.shortUrls.data.slice(0, ITEMS_IN_OVERVIEW_PAGE - 1)], - state, - )), - (state: ShortUrlsList) => (!state.shortUrls ? state : assocPath( - ['shortUrls', 'pagination', 'totalItems'], - state.shortUrls.pagination.totalItems + 1, - state, - )), - ), - // TODO Do not hardcode action type here - [`${SHORT_URL_EDITED}/fulfilled`]: (state, { payload: editedShortUrl }) => (!state.shortUrls ? state : assocPath( - ['shortUrls', 'data'], - state.shortUrls.data.map((shortUrl) => { - const { shortCode, domain } = editedShortUrl; +export const shortUrlsListReducerCreator = ( + listShortUrlsThunk: ReturnType, + editShortUrlThunk: ReturnType, + createShortUrlThunk: ReturnType, + deleteShortUrlThunk: ReturnType, +) => createSlice({ + name: REDUCER_PREFIX, + initialState, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(listShortUrlsThunk.pending, (state) => ({ ...state, loading: true, error: false })); + builder.addCase(listShortUrlsThunk.rejected, () => ({ loading: false, error: true })); + builder.addCase( + listShortUrlsThunk.fulfilled, + (_, { payload: shortUrls }) => ({ loading: false, error: false, shortUrls }), + ); - return shortUrlMatches(shortUrl, shortCode, domain) ? editedShortUrl : shortUrl; - }), - state, - )), -}, initialState); + builder.addCase( + createShortUrlThunk.fulfilled, + pipe( + // The only place where the list and the creation form coexist is the overview page. + // There we can assume we are displaying page 1, and therefore, we can safely prepend the new short URL. + // We can also remove the items above the amount that is displayed there. + (state, { payload }) => (!state.shortUrls ? state : assocPath( + ['shortUrls', 'data'], + [payload, ...state.shortUrls.data.slice(0, ITEMS_IN_OVERVIEW_PAGE - 1)], + state, + )), + (state: ShortUrlsList) => (!state.shortUrls ? state : assocPath( + ['shortUrls', 'pagination', 'totalItems'], + state.shortUrls.pagination.totalItems + 1, + state, + )), + ), + ); -export const listShortUrls = (buildShlinkApiClient: ShlinkApiClientBuilder) => ( - params: ShlinkShortUrlsListParams = {}, -) => async (dispatch: Dispatch, getState: GetState) => { - dispatch({ type: LIST_SHORT_URLS_START }); - const { listShortUrls: shlinkListShortUrls } = buildShlinkApiClient(getState); + builder.addCase( + editShortUrlThunk.fulfilled, + (state, { payload: editedShortUrl }) => (!state.shortUrls ? state : assocPath( + ['shortUrls', 'data'], + state.shortUrls.data.map((shortUrl) => { + const { shortCode, domain } = editedShortUrl; + return shortUrlMatches(shortUrl, shortCode, domain) ? editedShortUrl : shortUrl; + }), + state, + )), + ); - try { - const payload = await shlinkListShortUrls(params); + builder.addCase( + deleteShortUrlThunk.fulfilled, + pipe( + (state, { payload }) => (!state.shortUrls ? state : assocPath( + ['shortUrls', 'data'], + reject((shortUrl) => + shortUrlMatches(shortUrl, payload.shortCode, payload.domain), state.shortUrls.data), + state, + )), + (state) => (!state.shortUrls ? state : assocPath( + ['shortUrls', 'pagination', 'totalItems'], + state.shortUrls.pagination.totalItems - 1, + state, + )), + ), + ); - dispatch({ type: LIST_SHORT_URLS, payload }); - } catch (e) { - dispatch({ type: LIST_SHORT_URLS_ERROR }); - } -}; + builder.addCase( + createNewVisits, + (state, { payload }) => assocPath( + ['shortUrls', 'data'], + state.shortUrls?.data?.map( + (currentShortUrl) => { + // Find the last of the new visit for this short URL, and pick the amount of visits from it + const lastVisit = last( + payload.createdVisits.filter( + ({ shortUrl }) => shortUrl && shortUrlMatches(currentShortUrl, shortUrl.shortCode, shortUrl.domain), + ), + ); + + return lastVisit?.shortUrl + ? assoc('visitsCount', lastVisit.shortUrl.visitsCount, currentShortUrl) + : currentShortUrl; + }, + ), + state, + ), + ); + }, +}); diff --git a/src/short-urls/services/provideServices.ts b/src/short-urls/services/provideServices.ts index c9911d0d..4ddb4ea7 100644 --- a/src/short-urls/services/provideServices.ts +++ b/src/short-urls/services/provideServices.ts @@ -7,9 +7,9 @@ import { ShortUrlsRowMenu } from '../helpers/ShortUrlsRowMenu'; import { CreateShortUrl } from '../CreateShortUrl'; import { DeleteShortUrlModal } from '../helpers/DeleteShortUrlModal'; import { CreateShortUrlResult } from '../helpers/CreateShortUrlResult'; -import { listShortUrls } from '../reducers/shortUrlsList'; +import { listShortUrls, shortUrlsListReducerCreator } from '../reducers/shortUrlsList'; import { shortUrlCreationReducerCreator, createShortUrl } from '../reducers/shortUrlCreation'; -import { shortUrlDeletionReducerCreator } from '../reducers/shortUrlDeletion'; +import { shortUrlDeletionReducerCreator, deleteShortUrl } from '../reducers/shortUrlDeletion'; import { editShortUrl, shortUrlEditionReducerCreator } from '../reducers/shortUrlEdition'; import { shortUrlDetailReducerCreator } from '../reducers/shortUrlDetail'; import { ConnectDecorator } from '../../container/types'; @@ -57,12 +57,23 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.decorator('ExportShortUrlsBtn', connect(['selectedServer'])); // Reducers + bottle.serviceFactory( + 'shortUrlsListReducerCreator', + shortUrlsListReducerCreator, + 'listShortUrls', + 'editShortUrl', + 'createShortUrl', + 'deleteShortUrl', + ); + bottle.serviceFactory('shortUrlsListReducer', prop('reducer'), 'shortUrlsListReducerCreator'); + bottle.serviceFactory('shortUrlCreationReducerCreator', shortUrlCreationReducerCreator, 'createShortUrl'); bottle.serviceFactory('shortUrlCreationReducer', prop('reducer'), 'shortUrlCreationReducerCreator'); - bottle.serviceFactory('shortUrlEditionReducer', shortUrlEditionReducerCreator, 'editShortUrl'); + bottle.serviceFactory('shortUrlEditionReducerCreator', shortUrlEditionReducerCreator, 'editShortUrl'); + bottle.serviceFactory('shortUrlEditionReducer', prop('reducer'), 'shortUrlEditionReducerCreator'); - bottle.serviceFactory('shortUrlDeletionReducerCreator', shortUrlDeletionReducerCreator, 'buildShlinkApiClient'); + bottle.serviceFactory('shortUrlDeletionReducerCreator', shortUrlDeletionReducerCreator, 'deleteShortUrl'); bottle.serviceFactory('shortUrlDeletionReducer', prop('reducer'), 'shortUrlDeletionReducerCreator'); bottle.serviceFactory('shortUrlDetailReducerCreator', shortUrlDetailReducerCreator, 'buildShlinkApiClient'); @@ -74,7 +85,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('createShortUrl', createShortUrl, 'buildShlinkApiClient'); bottle.serviceFactory('resetCreateShortUrl', prop('resetCreateShortUrl'), 'shortUrlCreationReducerCreator'); - bottle.serviceFactory('deleteShortUrl', prop('deleteShortUrl'), 'shortUrlDeletionReducerCreator'); + bottle.serviceFactory('deleteShortUrl', deleteShortUrl, 'buildShlinkApiClient'); bottle.serviceFactory('resetDeleteShortUrl', prop('resetDeleteShortUrl'), 'shortUrlDeletionReducerCreator'); bottle.serviceFactory('getShortUrlDetail', prop('getShortUrlDetail'), 'shortUrlDetailReducerCreator'); diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts index 4f3dc6d8..cdd55d26 100644 --- a/src/tags/reducers/tagsList.ts +++ b/src/tags/reducers/tagsList.ts @@ -87,7 +87,7 @@ export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = t export const filterTags = createAction(`${REDUCER_PREFIX}/filterTags`); -export const reducer = ( +export const tagsListReducerCreator = ( listTagsThunk: ReturnType, createShortUrlThunk: ReturnType, ) => createSlice({ @@ -128,4 +128,4 @@ export const reducer = ( tags: stateTags.concat(payload.tags.filter((tag: string) => !stateTags.includes(tag))), // More performant than [ ...new Set(...) ] })); }, -}).reducer; +}); diff --git a/src/tags/services/provideServices.ts b/src/tags/services/provideServices.ts index ce1cc554..92980f72 100644 --- a/src/tags/services/provideServices.ts +++ b/src/tags/services/provideServices.ts @@ -5,7 +5,7 @@ import { TagCard } from '../TagCard'; import { DeleteTagConfirmModal } from '../helpers/DeleteTagConfirmModal'; import { EditTagModal } from '../helpers/EditTagModal'; import { TagsList } from '../TagsList'; -import { filterTags, listTags, reducer } from '../reducers/tagsList'; +import { filterTags, listTags, tagsListReducerCreator } from '../reducers/tagsList'; import { tagDeleted, tagDeleteReducerCreator } from '../reducers/tagDelete'; import { tagEdited, tagEditReducerCreator } from '../reducers/tagEdit'; import { ConnectDecorator } from '../../container/types'; @@ -44,7 +44,8 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('tagDeleteReducerCreator', tagDeleteReducerCreator, 'buildShlinkApiClient'); bottle.serviceFactory('tagDeleteReducer', prop('reducer'), 'tagDeleteReducerCreator'); - bottle.serviceFactory('tagsListReducer', reducer, 'listTags', 'createShortUrl'); + bottle.serviceFactory('tagsListReducerCreator', tagsListReducerCreator, 'listTags', 'createShortUrl'); + bottle.serviceFactory('tagsListReducer', prop('reducer'), 'tagsListReducerCreator'); // Actions const listTagsActionFactory = (force: boolean) => diff --git a/test/short-urls/reducers/shortUrlDeletion.test.ts b/test/short-urls/reducers/shortUrlDeletion.test.ts index fde38ff8..9a65734d 100644 --- a/test/short-urls/reducers/shortUrlDeletion.test.ts +++ b/test/short-urls/reducers/shortUrlDeletion.test.ts @@ -1,12 +1,16 @@ import { Mock } from 'ts-mockery'; -import { shortUrlDeletionReducerCreator } from '../../../src/short-urls/reducers/shortUrlDeletion'; +import { + shortUrlDeletionReducerCreator, + deleteShortUrl as deleteShortUrlCretor, +} from '../../../src/short-urls/reducers/shortUrlDeletion'; import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient'; import { ProblemDetailsError } from '../../../src/api/types/errors'; describe('shortUrlDeletionReducer', () => { const deleteShortUrlCall = jest.fn(); const buildShlinkApiClient = () => Mock.of({ deleteShortUrl: deleteShortUrlCall }); - const { reducer, resetDeleteShortUrl, deleteShortUrl } = shortUrlDeletionReducerCreator(buildShlinkApiClient); + const deleteShortUrl = deleteShortUrlCretor(buildShlinkApiClient); + const { reducer, resetDeleteShortUrl } = shortUrlDeletionReducerCreator(deleteShortUrl); beforeEach(jest.clearAllMocks); diff --git a/test/short-urls/reducers/shortUrlEdition.test.ts b/test/short-urls/reducers/shortUrlEdition.test.ts index 2e6d8396..941328c8 100644 --- a/test/short-urls/reducers/shortUrlEdition.test.ts +++ b/test/short-urls/reducers/shortUrlEdition.test.ts @@ -15,7 +15,7 @@ describe('shortUrlEditionReducer', () => { const updateShortUrl = jest.fn().mockResolvedValue(shortUrl); const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrl }); const editShortUrl = editShortUrlCreator(buildShlinkApiClient); - const reducer = shortUrlEditionReducerCreator(editShortUrl); + const { reducer } = shortUrlEditionReducerCreator(editShortUrl); afterEach(jest.clearAllMocks); diff --git a/test/short-urls/reducers/shortUrlsList.test.ts b/test/short-urls/reducers/shortUrlsList.test.ts index 954f98d6..cc9e748d 100644 --- a/test/short-urls/reducers/shortUrlsList.test.ts +++ b/test/short-urls/reducers/shortUrlsList.test.ts @@ -1,37 +1,44 @@ import { Mock } from 'ts-mockery'; -import reducer, { - LIST_SHORT_URLS, - LIST_SHORT_URLS_ERROR, - LIST_SHORT_URLS_START, - listShortUrls, +import { + listShortUrls as listShortUrlsCreator, + shortUrlsListReducerCreator, } from '../../../src/short-urls/reducers/shortUrlsList'; -import { SHORT_URL_DELETED } from '../../../src/short-urls/reducers/shortUrlDeletion'; +import { deleteShortUrl as deleteShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlDeletion'; +import { ShlinkPaginator, ShlinkShortUrlsResponse } from '../../../src/api/types'; +import { createShortUrl as createShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlCreation'; +import { editShortUrl as editShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlEdition'; +import { createNewVisits } from '../../../src/visits/reducers/visitCreation'; import { ShortUrl } from '../../../src/short-urls/data'; import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient'; -import { ShlinkPaginator, ShlinkShortUrlsResponse } from '../../../src/api/types'; -import { CREATE_SHORT_URL } from '../../../src/short-urls/reducers/shortUrlCreation'; -import { SHORT_URL_EDITED } from '../../../src/short-urls/reducers/shortUrlEdition'; -import { createNewVisits } from '../../../src/visits/reducers/visitCreation'; describe('shortUrlsListReducer', () => { const shortCode = 'abc123'; + const listShortUrlsMock = jest.fn(); + const buildShlinkApiClient = () => Mock.of({ listShortUrls: listShortUrlsMock }); + const listShortUrls = listShortUrlsCreator(buildShlinkApiClient); + const editShortUrl = editShortUrlCreator(buildShlinkApiClient); + const createShortUrl = createShortUrlCreator(buildShlinkApiClient); + const deleteShortUrl = deleteShortUrlCreator(buildShlinkApiClient); + const { reducer } = shortUrlsListReducerCreator(listShortUrls, editShortUrl, createShortUrl, deleteShortUrl); + + afterEach(jest.clearAllMocks); describe('reducer', () => { it('returns loading on LIST_SHORT_URLS_START', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS_START } as any)).toEqual({ + expect(reducer(undefined, { type: listShortUrls.pending.toString() })).toEqual({ loading: true, error: false, })); it('returns short URLs on LIST_SHORT_URLS', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS, payload: { data: [] } } as any)).toEqual({ + expect(reducer(undefined, { type: listShortUrls.fulfilled.toString(), payload: { data: [] } })).toEqual({ shortUrls: { data: [] }, loading: false, error: false, })); it('returns error on LIST_SHORT_URLS_ERROR', () => - expect(reducer(undefined, { type: LIST_SHORT_URLS_ERROR } as any)).toEqual({ + expect(reducer(undefined, { type: listShortUrls.rejected.toString() })).toEqual({ loading: false, error: true, })); @@ -52,7 +59,7 @@ describe('shortUrlsListReducer', () => { error: false, }; - expect(reducer(state, { type: `${SHORT_URL_DELETED}/fulfilled`, payload: { shortCode } } as any)).toEqual({ + expect(reducer(state, { type: deleteShortUrl.fulfilled.toString(), payload: { shortCode } })).toEqual({ shortUrls: { data: [{ shortCode, domain: 'example.com' }, { shortCode: 'foo' }], pagination: { totalItems: 9 }, @@ -85,7 +92,7 @@ describe('shortUrlsListReducer', () => { error: false, }; - expect(reducer(state, { type: createNewVisits.toString(), payload: { createdVisits } } as any)).toEqual({ + expect(reducer(state, { type: createNewVisits.toString(), payload: { createdVisits } })).toEqual({ shortUrls: { data: [ { shortCode, domain: 'example.com', visitsCount: 5 }, @@ -142,7 +149,7 @@ describe('shortUrlsListReducer', () => { error: false, }; - expect(reducer(state, { type: `${CREATE_SHORT_URL}/fulfilled`, payload: newShortUrl } as any)).toEqual({ + expect(reducer(state, { type: createShortUrl.fulfilled.toString(), payload: newShortUrl })).toEqual({ shortUrls: { data: expectedData, pagination: { totalItems: 16 }, @@ -181,7 +188,7 @@ describe('shortUrlsListReducer', () => { error: false, }; - const result = reducer(state, { type: `${SHORT_URL_EDITED}/fulfilled`, payload: editedShortUrl } as any); + const result = reducer(state, { type: editShortUrl.fulfilled.toString(), payload: editedShortUrl }); expect(result.shortUrls?.data).toEqual(expectedList); }); @@ -191,30 +198,29 @@ describe('shortUrlsListReducer', () => { const dispatch = jest.fn(); const getState = jest.fn().mockReturnValue({ selectedServer: {} }); - afterEach(jest.clearAllMocks); - it('dispatches proper actions if API client request succeeds', async () => { - const listShortUrlsMock = jest.fn().mockResolvedValue({}); - const apiClientMock = Mock.of({ listShortUrls: listShortUrlsMock }); + listShortUrlsMock.mockResolvedValue({}); - await listShortUrls(() => apiClientMock)()(dispatch, getState); + await listShortUrls()(dispatch, getState, {}); expect(dispatch).toHaveBeenCalledTimes(2); - expect(dispatch).toHaveBeenNthCalledWith(1, { type: LIST_SHORT_URLS_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: LIST_SHORT_URLS, payload: {} }); + expect(dispatch).toHaveBeenNthCalledWith(1, expect.objectContaining({ type: listShortUrls.pending.toString() })); + expect(dispatch).toHaveBeenNthCalledWith(2, expect.objectContaining({ + type: listShortUrls.fulfilled.toString(), + payload: {}, + })); expect(listShortUrlsMock).toHaveBeenCalledTimes(1); }); it('dispatches proper actions if API client request fails', async () => { - const listShortUrlsMock = jest.fn().mockRejectedValue(undefined); - const apiClientMock = Mock.of({ listShortUrls: listShortUrlsMock }); + listShortUrlsMock.mockRejectedValue(undefined); - await listShortUrls(() => apiClientMock)()(dispatch, getState); + await listShortUrls()(dispatch, getState, {}); expect(dispatch).toHaveBeenCalledTimes(2); - expect(dispatch).toHaveBeenNthCalledWith(1, { type: LIST_SHORT_URLS_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: LIST_SHORT_URLS_ERROR }); + expect(dispatch).toHaveBeenNthCalledWith(1, expect.objectContaining({ type: listShortUrls.pending.toString() })); + expect(dispatch).toHaveBeenNthCalledWith(2, expect.objectContaining({ type: listShortUrls.rejected.toString() })); expect(listShortUrlsMock).toHaveBeenCalledTimes(1); }); diff --git a/test/tags/reducers/tagsList.test.ts b/test/tags/reducers/tagsList.test.ts index 06c99828..a3f1cbb2 100644 --- a/test/tags/reducers/tagsList.test.ts +++ b/test/tags/reducers/tagsList.test.ts @@ -3,7 +3,7 @@ import { TagsList, filterTags, listTags as listTagsCreator, - reducer as reducerCreator, + tagsListReducerCreator, } from '../../../src/tags/reducers/tagsList'; import { ShlinkState } from '../../../src/container/types'; import { ShortUrl } from '../../../src/short-urls/data'; @@ -16,7 +16,7 @@ describe('tagsListReducer', () => { const buildShlinkApiClient = jest.fn(); const listTags = listTagsCreator(buildShlinkApiClient, true); const createShortUrl = createShortUrlCreator(buildShlinkApiClient); - const reducer = reducerCreator(listTags, createShortUrl); + const { reducer } = tagsListReducerCreator(listTags, createShortUrl); afterEach(jest.clearAllMocks);