Removed hardcoded action references by improving dependency injection

This commit is contained in:
Alejandro Celaya 2022-11-08 22:59:41 +01:00
parent f9bfb742da
commit 89423737e8
6 changed files with 26 additions and 19 deletions

View file

@ -35,12 +35,15 @@ const initialState: ShortUrlCreation = {
error: false, error: false,
}; };
export const shortUrlCreationReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => { export const createShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk(
const createShortUrl = createAsyncThunk(CREATE_SHORT_URL, (data: ShortUrlData, { getState }): Promise<ShortUrl> => { CREATE_SHORT_URL,
(data: ShortUrlData, { getState }): Promise<ShortUrl> => {
const { createShortUrl: shlinkCreateShortUrl } = buildShlinkApiClient(getState); const { createShortUrl: shlinkCreateShortUrl } = buildShlinkApiClient(getState);
return shlinkCreateShortUrl(data); return shlinkCreateShortUrl(data);
}); },
);
export const shortUrlCreationReducerCreator = (createShortUrlThunk: ReturnType<typeof createShortUrl>) => {
const { reducer, actions } = createSlice({ const { reducer, actions } = createSlice({
name: 'shortUrlCreationReducer', name: 'shortUrlCreationReducer',
initialState: initialState as ShortUrlCreation, // Without this casting it infers type ShortUrlCreationWaiting initialState: initialState as ShortUrlCreation, // Without this casting it infers type ShortUrlCreationWaiting
@ -48,13 +51,13 @@ export const shortUrlCreationReducerCreator = (buildShlinkApiClient: ShlinkApiCl
resetCreateShortUrl: () => initialState, resetCreateShortUrl: () => initialState,
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(createShortUrl.pending, () => ({ saving: true, saved: false, error: false })); builder.addCase(createShortUrlThunk.pending, () => ({ saving: true, saved: false, error: false }));
builder.addCase( builder.addCase(
createShortUrl.rejected, createShortUrlThunk.rejected,
(_, { error }) => ({ saving: false, saved: false, error: true, errorData: parseApiError(error) }), (_, { error }) => ({ saving: false, saved: false, error: true, errorData: parseApiError(error) }),
); );
builder.addCase( builder.addCase(
createShortUrl.fulfilled, createShortUrlThunk.fulfilled,
(_, { payload: result }) => ({ result, saving: false, saved: true, error: false }), (_, { payload: result }) => ({ result, saving: false, saved: true, error: false }),
); );
}, },
@ -64,7 +67,6 @@ export const shortUrlCreationReducerCreator = (buildShlinkApiClient: ShlinkApiCl
return { return {
reducer, reducer,
createShortUrl,
resetCreateShortUrl, resetCreateShortUrl,
}; };
}; };

View file

@ -8,7 +8,7 @@ import { CreateShortUrl } from '../CreateShortUrl';
import { DeleteShortUrlModal } from '../helpers/DeleteShortUrlModal'; import { DeleteShortUrlModal } from '../helpers/DeleteShortUrlModal';
import { CreateShortUrlResult } from '../helpers/CreateShortUrlResult'; import { CreateShortUrlResult } from '../helpers/CreateShortUrlResult';
import { listShortUrls } from '../reducers/shortUrlsList'; import { listShortUrls } from '../reducers/shortUrlsList';
import { shortUrlCreationReducerCreator } from '../reducers/shortUrlCreation'; import { shortUrlCreationReducerCreator, createShortUrl } from '../reducers/shortUrlCreation';
import { shortUrlDeletionReducerCreator } from '../reducers/shortUrlDeletion'; import { shortUrlDeletionReducerCreator } from '../reducers/shortUrlDeletion';
import { shortUrlEditionReducerCreator } from '../reducers/shortUrlEdition'; import { shortUrlEditionReducerCreator } from '../reducers/shortUrlEdition';
import { shortUrlDetailReducerCreator } from '../reducers/shortUrlDetail'; import { shortUrlDetailReducerCreator } from '../reducers/shortUrlDetail';
@ -57,7 +57,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.decorator('ExportShortUrlsBtn', connect(['selectedServer'])); bottle.decorator('ExportShortUrlsBtn', connect(['selectedServer']));
// Reducers // Reducers
bottle.serviceFactory('shortUrlCreationReducerCreator', shortUrlCreationReducerCreator, 'buildShlinkApiClient'); bottle.serviceFactory('shortUrlCreationReducerCreator', shortUrlCreationReducerCreator, 'createShortUrl');
bottle.serviceFactory('shortUrlCreationReducer', prop('reducer'), 'shortUrlCreationReducerCreator'); bottle.serviceFactory('shortUrlCreationReducer', prop('reducer'), 'shortUrlCreationReducerCreator');
bottle.serviceFactory('shortUrlEditionReducerCreator', shortUrlEditionReducerCreator, 'buildShlinkApiClient'); bottle.serviceFactory('shortUrlEditionReducerCreator', shortUrlEditionReducerCreator, 'buildShlinkApiClient');
@ -72,7 +72,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Actions // Actions
bottle.serviceFactory('listShortUrls', listShortUrls, 'buildShlinkApiClient'); bottle.serviceFactory('listShortUrls', listShortUrls, 'buildShlinkApiClient');
bottle.serviceFactory('createShortUrl', prop('createShortUrl'), 'shortUrlCreationReducerCreator'); bottle.serviceFactory('createShortUrl', createShortUrl, 'buildShlinkApiClient');
bottle.serviceFactory('resetCreateShortUrl', prop('resetCreateShortUrl'), 'shortUrlCreationReducerCreator'); bottle.serviceFactory('resetCreateShortUrl', prop('resetCreateShortUrl'), 'shortUrlCreationReducerCreator');
bottle.serviceFactory('deleteShortUrl', prop('deleteShortUrl'), 'shortUrlDeletionReducerCreator'); bottle.serviceFactory('deleteShortUrl', prop('deleteShortUrl'), 'shortUrlDeletionReducerCreator');

View file

@ -7,7 +7,7 @@ import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilde
import { CreateVisit, Stats } from '../../visits/types'; import { CreateVisit, Stats } from '../../visits/types';
import { parseApiError } from '../../api/utils'; import { parseApiError } from '../../api/utils';
import { TagStats } from '../data'; import { TagStats } from '../data';
import { CREATE_SHORT_URL } from '../../short-urls/reducers/shortUrlCreation'; import { createShortUrl } from '../../short-urls/reducers/shortUrlCreation';
import { tagDeleted } from './tagDelete'; import { tagDeleted } from './tagDelete';
import { tagEdited } from './tagEdit'; import { tagEdited } from './tagEdit';
import { ProblemDetailsError } from '../../api/types/errors'; import { ProblemDetailsError } from '../../api/types/errors';
@ -88,7 +88,10 @@ export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = t
export const filterTags = createAction<string>(FILTER_TAGS); export const filterTags = createAction<string>(FILTER_TAGS);
export const reducer = (listTagsThunk: ReturnType<typeof listTags>) => createSlice({ export const reducer = (
listTagsThunk: ReturnType<typeof listTags>,
createShortUrlThunk: ReturnType<typeof createShortUrl>,
) => createSlice({
name: 'shlink/tagsList', name: 'shlink/tagsList',
initialState, initialState,
reducers: {}, reducers: {},
@ -121,8 +124,7 @@ export const reducer = (listTagsThunk: ReturnType<typeof listTags>) => createSli
stats: increaseVisitsForTags(calculateVisitsPerTag(payload.createdVisits), state.stats), stats: increaseVisitsForTags(calculateVisitsPerTag(payload.createdVisits), state.stats),
})); }));
// TODO Do not hardcode action type here. Inject async thunk instead builder.addCase(createShortUrlThunk.fulfilled, ({ tags: stateTags, ...rest }, { payload }) => ({
builder.addCase(`${CREATE_SHORT_URL}/fulfilled`, ({ tags: stateTags, ...rest }, { payload }: any) => ({
...rest, ...rest,
tags: stateTags.concat(payload.tags.filter((tag: string) => !stateTags.includes(tag))), // More performant than [ ...new Set(...) ] tags: stateTags.concat(payload.tags.filter((tag: string) => !stateTags.includes(tag))), // More performant than [ ...new Set(...) ]
})); }));

View file

@ -44,7 +44,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('tagDeleteReducerCreator', tagDeleteReducerCreator, 'buildShlinkApiClient'); bottle.serviceFactory('tagDeleteReducerCreator', tagDeleteReducerCreator, 'buildShlinkApiClient');
bottle.serviceFactory('tagDeleteReducer', prop('reducer'), 'tagDeleteReducerCreator'); bottle.serviceFactory('tagDeleteReducer', prop('reducer'), 'tagDeleteReducerCreator');
bottle.serviceFactory('tagsListReducer', reducer, 'listTags'); bottle.serviceFactory('tagsListReducer', reducer, 'listTags', 'createShortUrl');
// Actions // Actions
const listTagsActionFactory = (force: boolean) => const listTagsActionFactory = (force: boolean) =>

View file

@ -2,6 +2,7 @@ import { Mock } from 'ts-mockery';
import { import {
CreateShortUrlAction, CreateShortUrlAction,
shortUrlCreationReducerCreator, shortUrlCreationReducerCreator,
createShortUrl as createShortUrlCreator,
} from '../../../src/short-urls/reducers/shortUrlCreation'; } from '../../../src/short-urls/reducers/shortUrlCreation';
import { ShortUrl } from '../../../src/short-urls/data'; import { ShortUrl } from '../../../src/short-urls/data';
import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient'; import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
@ -11,7 +12,8 @@ describe('shortUrlCreationReducer', () => {
const shortUrl = Mock.of<ShortUrl>(); const shortUrl = Mock.of<ShortUrl>();
const createShortUrlCall = jest.fn(); const createShortUrlCall = jest.fn();
const buildShlinkApiClient = () => Mock.of<ShlinkApiClient>({ createShortUrl: createShortUrlCall }); const buildShlinkApiClient = () => Mock.of<ShlinkApiClient>({ createShortUrl: createShortUrlCall });
const { reducer, createShortUrl, resetCreateShortUrl } = shortUrlCreationReducerCreator(buildShlinkApiClient); const createShortUrl = createShortUrlCreator(buildShlinkApiClient);
const { reducer, resetCreateShortUrl } = shortUrlCreationReducerCreator(createShortUrl);
afterEach(jest.resetAllMocks); afterEach(jest.resetAllMocks);

View file

@ -7,7 +7,7 @@ import {
} from '../../../src/tags/reducers/tagsList'; } from '../../../src/tags/reducers/tagsList';
import { ShlinkState } from '../../../src/container/types'; import { ShlinkState } from '../../../src/container/types';
import { ShortUrl } from '../../../src/short-urls/data'; import { ShortUrl } from '../../../src/short-urls/data';
import { CREATE_SHORT_URL } from '../../../src/short-urls/reducers/shortUrlCreation'; import { createShortUrl as createShortUrlCreator } from '../../../src/short-urls/reducers/shortUrlCreation';
import { tagEdited } from '../../../src/tags/reducers/tagEdit'; import { tagEdited } from '../../../src/tags/reducers/tagEdit';
import { tagDeleted } from '../../../src/tags/reducers/tagDelete'; import { tagDeleted } from '../../../src/tags/reducers/tagDelete';
@ -15,7 +15,8 @@ describe('tagsListReducer', () => {
const state = (props: Partial<TagsList>) => Mock.of<TagsList>(props); const state = (props: Partial<TagsList>) => Mock.of<TagsList>(props);
const buildShlinkApiClient = jest.fn(); const buildShlinkApiClient = jest.fn();
const listTags = listTagsCreator(buildShlinkApiClient, true); const listTags = listTagsCreator(buildShlinkApiClient, true);
const reducer = reducerCreator(listTags); const createShortUrl = createShortUrlCreator(buildShlinkApiClient);
const reducer = reducerCreator(listTags, createShortUrl);
afterEach(jest.clearAllMocks); afterEach(jest.clearAllMocks);
@ -99,7 +100,7 @@ describe('tagsListReducer', () => {
const tags = ['foo', 'bar', 'baz', 'foo2', 'fo']; const tags = ['foo', 'bar', 'baz', 'foo2', 'fo'];
const payload = Mock.of<ShortUrl>({ tags: shortUrlTags }); const payload = Mock.of<ShortUrl>({ tags: shortUrlTags });
expect(reducer(state({ tags }), { type: `${CREATE_SHORT_URL}/fulfilled`, payload })).toEqual({ expect(reducer(state({ tags }), { type: createShortUrl.fulfilled.toString(), payload })).toEqual({
tags: expectedTags, tags: expectedTags,
}); });
}); });