From 34f4411aa195754eee9face2df7f871738a262bb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 4 Nov 2022 18:56:34 +0100 Subject: [PATCH] Migrated domainRedirects reducer to redux/toolkit --- src/domains/reducers/domainRedirects.ts | 34 ++++++--------- src/domains/reducers/domainsList.ts | 23 ++++++----- src/domains/services/provideServices.ts | 15 ++++--- test/domains/reducers/domainRedirects.test.ts | 41 ++++++++----------- test/domains/reducers/domainsList.test.ts | 16 ++++---- 5 files changed, 61 insertions(+), 68 deletions(-) diff --git a/src/domains/reducers/domainRedirects.ts b/src/domains/reducers/domainRedirects.ts index 4a5472b9..aa97871b 100644 --- a/src/domains/reducers/domainRedirects.ts +++ b/src/domains/reducers/domainRedirects.ts @@ -1,33 +1,23 @@ -import { Action, Dispatch } from 'redux'; +import { createAsyncThunk } from '@reduxjs/toolkit'; import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import { ShlinkDomainRedirects } from '../../api/types'; -import { GetState } from '../../container/types'; -import { ApiErrorAction } from '../../api/types/actions'; -import { parseApiError } from '../../api/utils'; +import { ShlinkState } from '../../container/types'; -export const EDIT_DOMAIN_REDIRECTS_START = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS_START'; -export const EDIT_DOMAIN_REDIRECTS_ERROR = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS_ERROR'; -export const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS'; +const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS'; export interface EditDomainRedirects { domain: string; redirects: ShlinkDomainRedirects; } -export interface EditDomainRedirectsAction extends Action, EditDomainRedirects {} - -export const editDomainRedirects = (buildShlinkApiClient: ShlinkApiClientBuilder) => ({ - domain, - redirects: domainRedirects, -}: EditDomainRedirects) => async (dispatch: Dispatch, getState: GetState) => { - dispatch({ type: EDIT_DOMAIN_REDIRECTS_START }); - const { editDomainRedirects: shlinkEditDomainRedirects } = buildShlinkApiClient(getState); - - try { +export const editDomainRedirects = ( + buildShlinkApiClient: ShlinkApiClientBuilder, +) => createAsyncThunk( + EDIT_DOMAIN_REDIRECTS, + async ({ domain, redirects: domainRedirects }, { getState }) => { + const { editDomainRedirects: shlinkEditDomainRedirects } = buildShlinkApiClient(getState); const redirects = await shlinkEditDomainRedirects({ domain, ...domainRedirects }); - dispatch({ type: EDIT_DOMAIN_REDIRECTS, domain, redirects }); - } catch (e: any) { - dispatch({ type: EDIT_DOMAIN_REDIRECTS_ERROR, errorData: parseApiError(e) }); - } -}; + return { domain, redirects }; + }, +); diff --git a/src/domains/reducers/domainsList.ts b/src/domains/reducers/domainsList.ts index de1df166..bf7f8758 100644 --- a/src/domains/reducers/domainsList.ts +++ b/src/domains/reducers/domainsList.ts @@ -1,17 +1,17 @@ -import { createSlice, createAsyncThunk, createAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import { createSlice, createAsyncThunk, createAction, SliceCaseReducers, AsyncThunk } from '@reduxjs/toolkit'; import { ShlinkDomainRedirects } from '../../api/types'; import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import { ShlinkState } from '../../container/types'; import { Domain, DomainStatus } from '../data'; import { hasServerData } from '../../servers/data'; import { replaceAuthorityFromUri } from '../../utils/helpers/uri'; -import { EDIT_DOMAIN_REDIRECTS } from './domainRedirects'; import { ProblemDetailsError } from '../../api/types/errors'; import { parseApiError } from '../../api/utils'; +import { EditDomainRedirects } from './domainRedirects'; -export const LIST_DOMAINS = 'shlink/domainsList/LIST_DOMAINS'; -export const FILTER_DOMAINS = 'shlink/domainsList/FILTER_DOMAINS'; -export const VALIDATE_DOMAIN = 'shlink/domainsList/VALIDATE_DOMAIN'; +const LIST_DOMAINS = 'shlink/domainsList/LIST_DOMAINS'; +const FILTER_DOMAINS = 'shlink/domainsList/FILTER_DOMAINS'; +const VALIDATE_DOMAIN = 'shlink/domainsList/VALIDATE_DOMAIN'; export interface DomainsList { domains: Domain[]; @@ -39,13 +39,16 @@ const initialState: DomainsList = { error: false, }; -export const replaceRedirectsOnDomain = (domain: string, redirects: ShlinkDomainRedirects) => +export const replaceRedirectsOnDomain = ({ domain, redirects }: EditDomainRedirects) => (d: Domain): Domain => (d.domain !== domain ? d : { ...d, redirects }); export const replaceStatusOnDomain = (domain: string, status: DomainStatus) => (d: Domain): Domain => (d.domain !== domain ? d : { ...d, status }); -export const domainsListReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => { +export const domainsListReducerCreator = ( + buildShlinkApiClient: ShlinkApiClientBuilder, + editDomainRedirects: AsyncThunk, +) => { const listDomains = createAsyncThunk( LIST_DOMAINS, async (_, { getState }) => { @@ -110,10 +113,10 @@ export const domainsListReducerCreator = (buildShlinkApiClient: ShlinkApiClientB filteredDomains: state.domains.filter(({ domain }) => domain.toLowerCase().match(payload.toLowerCase())), })); - builder.addCase(EDIT_DOMAIN_REDIRECTS, (state, { domain, redirects }: any) => ({ // TODO Fix this "any" + builder.addCase(editDomainRedirects.fulfilled, (state, { payload }) => ({ ...state, - domains: state.domains.map(replaceRedirectsOnDomain(domain, redirects)), - filteredDomains: state.filteredDomains.map(replaceRedirectsOnDomain(domain, redirects)), + domains: state.domains.map(replaceRedirectsOnDomain(payload)), + filteredDomains: state.filteredDomains.map(replaceRedirectsOnDomain(payload)), })); }, }); diff --git a/src/domains/services/provideServices.ts b/src/domains/services/provideServices.ts index f03a9c30..827302a3 100644 --- a/src/domains/services/provideServices.ts +++ b/src/domains/services/provideServices.ts @@ -18,14 +18,19 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { )); // Reducer - bottle.serviceFactory('domainsListReducerCreator', domainsListReducerCreator, 'buildShlinkApiClient'); - bottle.serviceFactory('domainsListReducer', prop('reducer'), 'domainsListReducerCreator'); // TODO Improve type checks on the prop that gets picked here + bottle.serviceFactory( + 'domainsListReducerCreator', + domainsListReducerCreator, + 'buildShlinkApiClient', + 'editDomainRedirects', + ); + bottle.serviceFactory('domainsListReducer', prop('reducer'), 'domainsListReducerCreator'); // Actions - bottle.serviceFactory('listDomains', prop('listDomains'), 'domainsListReducerCreator'); // TODO Improve type checks on the prop that gets picked here - bottle.serviceFactory('filterDomains', prop('filterDomains'), 'domainsListReducerCreator'); // TODO Improve type checks on the prop that gets picked here + bottle.serviceFactory('listDomains', prop('listDomains'), 'domainsListReducerCreator'); + bottle.serviceFactory('filterDomains', prop('filterDomains'), 'domainsListReducerCreator'); bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'buildShlinkApiClient'); - bottle.serviceFactory('checkDomainHealth', prop('checkDomainHealth'), 'domainsListReducerCreator'); // TODO Improve type checks on the prop that gets picked here + bottle.serviceFactory('checkDomainHealth', prop('checkDomainHealth'), 'domainsListReducerCreator'); }; export default provideServices; diff --git a/test/domains/reducers/domainRedirects.test.ts b/test/domains/reducers/domainRedirects.test.ts index c7ed433d..567e0c1f 100644 --- a/test/domains/reducers/domainRedirects.test.ts +++ b/test/domains/reducers/domainRedirects.test.ts @@ -1,11 +1,6 @@ import { Mock } from 'ts-mockery'; import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient'; -import { - EDIT_DOMAIN_REDIRECTS, - EDIT_DOMAIN_REDIRECTS_ERROR, - EDIT_DOMAIN_REDIRECTS_START, EditDomainRedirects, - editDomainRedirects as editDomainRedirectsAction, -} from '../../../src/domains/reducers/domainRedirects'; +import { EditDomainRedirects, editDomainRedirects } from '../../../src/domains/reducers/domainRedirects'; import { ShlinkDomainRedirects } from '../../../src/api/types'; describe('domainRedirectsReducer', () => { @@ -16,35 +11,33 @@ describe('domainRedirectsReducer', () => { const redirects = Mock.all(); const dispatch = jest.fn(); const getState = jest.fn(); - const editDomainRedirects = jest.fn(); - const buildShlinkApiClient = () => Mock.of({ editDomainRedirects }); + const editDomainRedirectsCall = jest.fn(); + const buildShlinkApiClient = () => Mock.of({ editDomainRedirects: editDomainRedirectsCall }); + const editDomainRedirectsAction = editDomainRedirects(buildShlinkApiClient); it('dispatches error when loading domains fails', async () => { - editDomainRedirects.mockRejectedValue(new Error('error')); + editDomainRedirectsCall.mockRejectedValue(new Error('error')); - await editDomainRedirectsAction(buildShlinkApiClient)(Mock.of({ domain }))( - dispatch, - getState, - ); + await editDomainRedirectsAction(Mock.of({ domain }))(dispatch, getState, {}); expect(dispatch).toHaveBeenCalledTimes(2); - expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_DOMAIN_REDIRECTS_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: EDIT_DOMAIN_REDIRECTS_ERROR }); - expect(editDomainRedirects).toHaveBeenCalledTimes(1); + expect(dispatch).toHaveBeenLastCalledWith(expect.objectContaining({ + type: editDomainRedirectsAction.rejected.toString(), + })); + expect(editDomainRedirectsCall).toHaveBeenCalledTimes(1); }); it('dispatches domain and redirects once loaded', async () => { - editDomainRedirects.mockResolvedValue(redirects); + editDomainRedirectsCall.mockResolvedValue(redirects); - await editDomainRedirectsAction(buildShlinkApiClient)(Mock.of({ domain }))( - dispatch, - getState, - ); + await editDomainRedirectsAction(Mock.of({ domain }))(dispatch, getState, {}); expect(dispatch).toHaveBeenCalledTimes(2); - expect(dispatch).toHaveBeenNthCalledWith(1, { type: EDIT_DOMAIN_REDIRECTS_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: EDIT_DOMAIN_REDIRECTS, domain, redirects }); - expect(editDomainRedirects).toHaveBeenCalledTimes(1); + expect(dispatch).toHaveBeenLastCalledWith(expect.objectContaining({ + type: editDomainRedirectsAction.fulfilled.toString(), + payload: { domain, redirects }, + })); + expect(editDomainRedirectsCall).toHaveBeenCalledTimes(1); }); }); }); diff --git a/test/domains/reducers/domainsList.test.ts b/test/domains/reducers/domainsList.test.ts index dac4a87d..7efb6b00 100644 --- a/test/domains/reducers/domainsList.test.ts +++ b/test/domains/reducers/domainsList.test.ts @@ -6,7 +6,7 @@ import { replaceStatusOnDomain, domainsListReducerCreator, } from '../../../src/domains/reducers/domainsList'; -import { EDIT_DOMAIN_REDIRECTS } from '../../../src/domains/reducers/domainRedirects'; +import { editDomainRedirects } from '../../../src/domains/reducers/domainRedirects'; import { ShlinkDomainRedirects } from '../../../src/api/types'; import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient'; import { Domain } from '../../../src/domains/data'; @@ -30,8 +30,10 @@ describe('domainsListReducer', () => { data: { type: 'NOT_FOUND', status: 404 }, }, }); + const editDomainRedirectsThunk = editDomainRedirects(buildShlinkApiClient); const { reducer, listDomains: listDomainsAction, checkDomainHealth, filterDomains } = domainsListReducerCreator( buildShlinkApiClient, + editDomainRedirectsThunk, ); beforeEach(jest.clearAllMocks); @@ -72,12 +74,12 @@ describe('domainsListReducer', () => { invalidShortUrlRedirect: null, }; - expect(reducer( - Mock.of({ domains, filteredDomains }), - { type: EDIT_DOMAIN_REDIRECTS, domain, redirects }, - )).toEqual({ - domains: domains.map(replaceRedirectsOnDomain(domain, redirects)), - filteredDomains: filteredDomains.map(replaceRedirectsOnDomain(domain, redirects)), + expect(reducer(Mock.of({ domains, filteredDomains }), { + type: editDomainRedirectsThunk.fulfilled.toString(), + payload: { domain, redirects }, + })).toEqual({ + domains: domains.map(replaceRedirectsOnDomain({ domain, redirects })), + filteredDomains: filteredDomains.map(replaceRedirectsOnDomain({ domain, redirects })), }); });