Added meaningful error messages for the rest of API calls

This commit is contained in:
Alejandro Celaya 2020-12-21 23:41:50 +01:00
parent ee95d5a1b7
commit 4c3772d5c8
13 changed files with 82 additions and 33 deletions

View file

@ -5,6 +5,7 @@ import SearchField from '../utils/SearchField';
import { SelectedServer } from '../servers/data'; import { SelectedServer } from '../servers/data';
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
import { Result } from '../utils/Result'; import { Result } from '../utils/Result';
import { ShlinkApiError } from '../api/ShlinkApiError';
import { TagsList as TagsListState } from './reducers/tagsList'; import { TagsList as TagsListState } from './reducers/tagsList';
import { TagCardProps } from './TagCard'; import { TagCardProps } from './TagCard';
@ -33,7 +34,11 @@ const TagsList = (TagCard: FC<TagCardProps>) => boundToMercureHub((
} }
if (tagsList.error) { if (tagsList.error) {
return <Result type="error">Error loading tags :(</Result>; return (
<Result type="error">
<ShlinkApiError errorData={tagsList.errorData} fallbackMessage="Error loading tags :(" />
</Result>
);
} }
const tagsCount = tagsList.filteredTags.length; const tagsCount = tagsList.filteredTags.length;

View file

@ -2,6 +2,7 @@ import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { TagDeletion } from '../reducers/tagDelete'; import { TagDeletion } from '../reducers/tagDelete';
import { TagModalProps } from '../data'; import { TagModalProps } from '../data';
import { Result } from '../../utils/Result'; import { Result } from '../../utils/Result';
import { ShlinkApiError } from '../../api/ShlinkApiError';
interface DeleteTagConfirmModalProps extends TagModalProps { interface DeleteTagConfirmModalProps extends TagModalProps {
deleteTag: (tag: string) => Promise<void>; deleteTag: (tag: string) => Promise<void>;
@ -12,6 +13,7 @@ interface DeleteTagConfirmModalProps extends TagModalProps {
const DeleteTagConfirmModal = ( const DeleteTagConfirmModal = (
{ tag, toggle, isOpen, deleteTag, tagDelete, tagDeleted }: DeleteTagConfirmModalProps, { tag, toggle, isOpen, deleteTag, tagDelete, tagDeleted }: DeleteTagConfirmModalProps,
) => { ) => {
const { deleting, error, errorData } = tagDelete;
const doDelete = async () => { const doDelete = async () => {
await deleteTag(tag); await deleteTag(tag);
tagDeleted(tag); tagDeleted(tag);
@ -25,16 +27,16 @@ const DeleteTagConfirmModal = (
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
Are you sure you want to delete tag <b>{tag}</b>? Are you sure you want to delete tag <b>{tag}</b>?
{tagDelete.error && ( {error && (
<Result type="error" small className="mt-2"> <Result type="error" small className="mt-2">
Something went wrong while deleting the tag :( <ShlinkApiError errorData={errorData} fallbackMessage="Something went wrong while deleting the tag :(" />
</Result> </Result>
)} )}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<button className="btn btn-link" onClick={toggle}>Cancel</button> <button className="btn btn-link" onClick={toggle}>Cancel</button>
<button className="btn btn-danger" disabled={tagDelete.deleting} onClick={doDelete}> <button className="btn btn-danger" disabled={deleting} onClick={doDelete}>
{tagDelete.deleting ? 'Deleting tag...' : 'Delete tag'} {deleting ? 'Deleting tag...' : 'Delete tag'}
</button> </button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>

View file

@ -8,8 +8,9 @@ import { handleEventPreventingDefault } from '../../utils/utils';
import ColorGenerator from '../../utils/services/ColorGenerator'; import ColorGenerator from '../../utils/services/ColorGenerator';
import { TagModalProps } from '../data'; import { TagModalProps } from '../data';
import { TagEdition } from '../reducers/tagEdit'; import { TagEdition } from '../reducers/tagEdit';
import './EditTagModal.scss';
import { Result } from '../../utils/Result'; import { Result } from '../../utils/Result';
import { ShlinkApiError } from '../../api/ShlinkApiError';
import './EditTagModal.scss';
interface EditTagModalProps extends TagModalProps { interface EditTagModalProps extends TagModalProps {
tagEdit: TagEdition; tagEdit: TagEdition;
@ -23,6 +24,7 @@ const EditTagModal = ({ getColorForKey }: ColorGenerator) => (
const [ newTagName, setNewTagName ] = useState(tag); const [ newTagName, setNewTagName ] = useState(tag);
const [ color, setColor ] = useState(getColorForKey(tag)); const [ color, setColor ] = useState(getColorForKey(tag));
const [ showColorPicker, toggleColorPicker, , hideColorPicker ] = useToggle(); const [ showColorPicker, toggleColorPicker, , hideColorPicker ] = useToggle();
const { editing, error, errorData } = tagEdit;
const saveTag = handleEventPreventingDefault(async () => editTag(tag, newTagName, color) const saveTag = handleEventPreventingDefault(async () => editTag(tag, newTagName, color)
.then(() => tagEdited(tag, newTagName, color)) .then(() => tagEdited(tag, newTagName, color))
.then(toggle) .then(toggle)
@ -55,17 +57,15 @@ const EditTagModal = ({ getColorForKey }: ColorGenerator) => (
/> />
</div> </div>
{tagEdit.error && ( {error && (
<Result type="error" small className="mt-2"> <Result type="error" small className="mt-2">
Something went wrong while editing the tag :( <ShlinkApiError errorData={errorData} fallbackMessage="Something went wrong while editing the tag :(" />
</Result> </Result>
)} )}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<button type="button" className="btn btn-link" onClick={toggle}>Cancel</button> <button type="button" className="btn btn-link" onClick={toggle}>Cancel</button>
<button type="submit" className="btn btn-primary" disabled={tagEdit.editing}> <button type="submit" className="btn btn-primary" disabled={editing}>{editing ? 'Saving...' : 'Save'}</button>
{tagEdit.editing ? 'Saving...' : 'Save'}
</button>
</ModalFooter> </ModalFooter>
</form> </form>
</Modal> </Modal>

View file

@ -2,6 +2,7 @@ import { Action, Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
import { ProblemDetailsError } from '../../utils/services/types';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START'; export const DELETE_TAG_START = 'shlink/deleteTag/DELETE_TAG_START';
@ -13,20 +14,25 @@ export const TAG_DELETED = 'shlink/deleteTag/TAG_DELETED';
export interface TagDeletion { export interface TagDeletion {
deleting: boolean; deleting: boolean;
error: boolean; error: boolean;
errorData?: ProblemDetailsError;
} }
export interface DeleteTagAction extends Action<string> { export interface DeleteTagAction extends Action<string> {
tag: string; tag: string;
} }
export interface DeleteTagFailedAction extends Action<string> {
errorData?: ProblemDetailsError;
}
const initialState: TagDeletion = { const initialState: TagDeletion = {
deleting: false, deleting: false,
error: false, error: false,
}; };
export default buildReducer({ export default buildReducer<TagDeletion, DeleteTagFailedAction>({
[DELETE_TAG_START]: () => ({ deleting: true, error: false }), [DELETE_TAG_START]: () => ({ deleting: true, error: false }),
[DELETE_TAG_ERROR]: () => ({ deleting: false, error: true }), [DELETE_TAG_ERROR]: (_, { errorData }) => ({ deleting: false, error: true, errorData }),
[DELETE_TAG]: () => ({ deleting: false, error: false }), [DELETE_TAG]: () => ({ deleting: false, error: false }),
}, initialState); }, initialState);
@ -41,7 +47,7 @@ export const deleteTag = (buildShlinkApiClient: ShlinkApiClientBuilder) => (tag:
await deleteTags([ tag ]); await deleteTags([ tag ]);
dispatch({ type: DELETE_TAG }); dispatch({ type: DELETE_TAG });
} catch (e) { } catch (e) {
dispatch({ type: DELETE_TAG_ERROR }); dispatch<DeleteTagFailedAction>({ type: DELETE_TAG_ERROR, errorData: e.response?.data });
throw e; throw e;
} }

View file

@ -4,6 +4,7 @@ import { buildReducer } from '../../utils/helpers/redux';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import ColorGenerator from '../../utils/services/ColorGenerator'; import ColorGenerator from '../../utils/services/ColorGenerator';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
import { ProblemDetailsError } from '../../utils/services/types';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START'; export const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START';
@ -18,6 +19,7 @@ export interface TagEdition {
newName: string; newName: string;
editing: boolean; editing: boolean;
error: boolean; error: boolean;
errorData?: ProblemDetailsError;
} }
export interface EditTagAction extends Action<string> { export interface EditTagAction extends Action<string> {
@ -26,6 +28,10 @@ export interface EditTagAction extends Action<string> {
color: string; color: string;
} }
export interface EditTagFailedAction extends Action<string> {
errorData?: ProblemDetailsError;
}
const initialState: TagEdition = { const initialState: TagEdition = {
oldName: '', oldName: '',
newName: '', newName: '',
@ -33,9 +39,9 @@ const initialState: TagEdition = {
error: false, error: false,
}; };
export default buildReducer<TagEdition, EditTagAction>({ export default buildReducer<TagEdition, EditTagAction & EditTagFailedAction>({
[EDIT_TAG_START]: (state) => ({ ...state, editing: true, error: false }), [EDIT_TAG_START]: (state) => ({ ...state, editing: true, error: false }),
[EDIT_TAG_ERROR]: (state) => ({ ...state, editing: false, error: true }), [EDIT_TAG_ERROR]: (state, { errorData }) => ({ ...state, editing: false, error: true, errorData }),
[EDIT_TAG]: (_, action) => ({ [EDIT_TAG]: (_, action) => ({
...pick([ 'oldName', 'newName' ], action), ...pick([ 'oldName', 'newName' ], action),
editing: false, editing: false,
@ -56,7 +62,7 @@ export const editTag = (buildShlinkApiClient: ShlinkApiClientBuilder, colorGener
colorGenerator.setColorForKey(newName, color); colorGenerator.setColorForKey(newName, color);
dispatch({ type: EDIT_TAG, oldName, newName }); dispatch({ type: EDIT_TAG, oldName, newName });
} catch (e) { } catch (e) {
dispatch({ type: EDIT_TAG_ERROR }); dispatch<EditTagFailedAction>({ type: EDIT_TAG_ERROR, errorData: e.response?.data });
throw e; throw e;
} }

View file

@ -2,7 +2,7 @@ import { isEmpty, reject } from 'ramda';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { CREATE_VISITS, CreateVisitsAction } from '../../visits/reducers/visitCreation'; import { CREATE_VISITS, CreateVisitsAction } from '../../visits/reducers/visitCreation';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkTags } from '../../utils/services/types'; import { ProblemDetailsError, ShlinkTags } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
import { TagStats } from '../data'; import { TagStats } from '../data';
@ -25,6 +25,7 @@ export interface TagsList {
stats: TagsStatsMap; stats: TagsStatsMap;
loading: boolean; loading: boolean;
error: boolean; error: boolean;
errorData?: ProblemDetailsError;
} }
interface ListTagsAction extends Action<string> { interface ListTagsAction extends Action<string> {
@ -32,11 +33,21 @@ interface ListTagsAction extends Action<string> {
stats: TagsStatsMap; stats: TagsStatsMap;
} }
interface ListTagsFailedAction extends Action<string> {
errorData?: ProblemDetailsError;
}
interface FilterTagsAction extends Action<string> { interface FilterTagsAction extends Action<string> {
searchTerm: string; searchTerm: string;
} }
type ListTagsCombinedAction = ListTagsAction & DeleteTagAction & CreateVisitsAction & EditTagAction & FilterTagsAction; type ListTagsCombinedAction = ListTagsAction
& DeleteTagAction
& CreateVisitsAction
& EditTagAction
& FilterTagsAction
& ListTagsFailedAction;
const initialState = { const initialState = {
tags: [], tags: [],
@ -74,7 +85,7 @@ const calculateVisitsPerTag = (createdVisits: CreateVisit[]): TagIncrease[] => O
export default buildReducer<TagsList, ListTagsCombinedAction>({ export default buildReducer<TagsList, ListTagsCombinedAction>({
[LIST_TAGS_START]: () => ({ ...initialState, loading: true }), [LIST_TAGS_START]: () => ({ ...initialState, loading: true }),
[LIST_TAGS_ERROR]: () => ({ ...initialState, error: true }), [LIST_TAGS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
[LIST_TAGS]: (_, { tags, stats }) => ({ ...initialState, stats, tags, filteredTags: tags }), [LIST_TAGS]: (_, { tags, stats }) => ({ ...initialState, stats, tags, filteredTags: tags }),
[TAG_DELETED]: (state, { tag }) => ({ [TAG_DELETED]: (state, { tag }) => ({
...state, ...state,
@ -119,7 +130,7 @@ export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = t
dispatch<ListTagsAction>({ tags, stats: processedStats, type: LIST_TAGS }); dispatch<ListTagsAction>({ tags, stats: processedStats, type: LIST_TAGS });
} catch (e) { } catch (e) {
dispatch({ type: LIST_TAGS_ERROR }); dispatch<ListTagsFailedAction>({ type: LIST_TAGS_ERROR, errorData: e.response?.data });
} }
}; };

View file

@ -12,6 +12,7 @@ import { formatIsoDate } from '../utils/helpers/date';
import { ShlinkVisitsParams } from '../utils/services/types'; import { ShlinkVisitsParams } from '../utils/services/types';
import { DateInterval, DateRange, intervalToDateRange } from '../utils/dates/types'; import { DateInterval, DateRange, intervalToDateRange } from '../utils/dates/types';
import { Result } from '../utils/Result'; import { Result } from '../utils/Result';
import { ShlinkApiError } from '../api/ShlinkApiError';
import SortableBarGraph from './helpers/SortableBarGraph'; import SortableBarGraph from './helpers/SortableBarGraph';
import GraphCard from './helpers/GraphCard'; import GraphCard from './helpers/GraphCard';
import LineChartCard from './helpers/LineChartCard'; import LineChartCard from './helpers/LineChartCard';
@ -83,7 +84,7 @@ const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, ca
return !subPath ? `${baseUrl}${query}` : `${baseUrl}${subPath}${query}`; return !subPath ? `${baseUrl}${query}` : `${baseUrl}${subPath}${query}`;
}; };
const { visits, loading, loadingLarge, error, progress } = visitsInfo; const { visits, loading, loadingLarge, error, errorData, progress } = visitsInfo;
const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]); const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]);
const { os, browsers, referrers, countries, cities, citiesForMap } = useMemo( const { os, browsers, referrers, countries, cities, citiesForMap } = useMemo(
() => processStatsFromVisits(normalizedVisits), () => processStatsFromVisits(normalizedVisits),
@ -131,7 +132,11 @@ const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, ca
} }
if (error) { if (error) {
return <Result type="error">An error occurred while loading visits :(</Result>; return (
<Result type="error">
<ShlinkApiError errorData={errorData} fallbackMessage="An error occurred while loading visits :(" />
</Result>
);
} }
if (isEmpty(visits)) { if (isEmpty(visits)) {

View file

@ -1,7 +1,7 @@
import { flatten, prop, range, splitEvery } from 'ramda'; import { flatten, prop, range, splitEvery } from 'ramda';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { ShlinkPaginator, ShlinkVisits } from '../../utils/services/types'; import { ShlinkPaginator, ShlinkVisits } from '../../utils/services/types';
import { Visit } from '../types'; import { Visit, VisitsLoadFailedAction } from '../types';
const ITEMS_PER_PAGE = 5000; const ITEMS_PER_PAGE = 5000;
const PARALLEL_REQUESTS_COUNT = 4; const PARALLEL_REQUESTS_COUNT = 4;
@ -71,6 +71,6 @@ export const getVisitsWithLoader = async <T extends Action<string> & { visits: V
dispatch({ ...extraFinishActionData, visits, type: actionMap.finish }); dispatch({ ...extraFinishActionData, visits, type: actionMap.finish });
} catch (e) { } catch (e) {
dispatch({ type: actionMap.error }); dispatch<VisitsLoadFailedAction>({ type: actionMap.error, errorData: e.response?.data });
} }
}; };

View file

@ -1,6 +1,6 @@
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { shortUrlMatches } from '../../short-urls/helpers'; import { shortUrlMatches } from '../../short-urls/helpers';
import { Visit, VisitsInfo, VisitsLoadProgressChangedAction } from '../types'; import { Visit, VisitsInfo, VisitsLoadFailedAction, VisitsLoadProgressChangedAction } from '../types';
import { ShortUrlIdentifier } from '../../short-urls/data'; import { ShortUrlIdentifier } from '../../short-urls/data';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
@ -24,7 +24,10 @@ interface ShortUrlVisitsAction extends Action<string>, ShortUrlIdentifier {
visits: Visit[]; visits: Visit[];
} }
type ShortUrlVisitsCombinedAction = ShortUrlVisitsAction & VisitsLoadProgressChangedAction & CreateVisitsAction; type ShortUrlVisitsCombinedAction = ShortUrlVisitsAction
& VisitsLoadProgressChangedAction
& CreateVisitsAction
& VisitsLoadFailedAction;
const initialState: ShortUrlVisits = { const initialState: ShortUrlVisits = {
visits: [], visits: [],
@ -39,7 +42,7 @@ const initialState: ShortUrlVisits = {
export default buildReducer<ShortUrlVisits, ShortUrlVisitsCombinedAction>({ export default buildReducer<ShortUrlVisits, ShortUrlVisitsCombinedAction>({
[GET_SHORT_URL_VISITS_START]: () => ({ ...initialState, loading: true }), [GET_SHORT_URL_VISITS_START]: () => ({ ...initialState, loading: true }),
[GET_SHORT_URL_VISITS_ERROR]: () => ({ ...initialState, error: true }), [GET_SHORT_URL_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
[GET_SHORT_URL_VISITS]: (_, { visits, shortCode, domain }) => ({ [GET_SHORT_URL_VISITS]: (_, { visits, shortCode, domain }) => ({
...initialState, ...initialState,
visits, visits,

View file

@ -1,5 +1,5 @@
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { Visit, VisitsInfo, VisitsLoadProgressChangedAction } from '../types'; import { Visit, VisitsInfo, VisitsLoadFailedAction, VisitsLoadProgressChangedAction } from '../types';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder'; import { ShlinkApiClientBuilder } from '../../utils/services/ShlinkApiClientBuilder';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
@ -24,6 +24,11 @@ export interface TagVisitsAction extends Action<string> {
tag: string; tag: string;
} }
type TagsVisitsCombinedAction = TagVisitsAction
& VisitsLoadProgressChangedAction
& CreateVisitsAction
& VisitsLoadFailedAction;
const initialState: TagVisits = { const initialState: TagVisits = {
visits: [], visits: [],
tag: '', tag: '',
@ -34,9 +39,9 @@ const initialState: TagVisits = {
progress: 0, progress: 0,
}; };
export default buildReducer<TagVisits, TagVisitsAction & VisitsLoadProgressChangedAction & CreateVisitsAction>({ export default buildReducer<TagVisits, TagsVisitsCombinedAction>({
[GET_TAG_VISITS_START]: () => ({ ...initialState, loading: true }), [GET_TAG_VISITS_START]: () => ({ ...initialState, loading: true }),
[GET_TAG_VISITS_ERROR]: () => ({ ...initialState, error: true }), [GET_TAG_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
[GET_TAG_VISITS]: (_, { visits, tag }) => ({ ...initialState, visits, tag }), [GET_TAG_VISITS]: (_, { visits, tag }) => ({ ...initialState, visits, tag }),
[GET_TAG_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }), [GET_TAG_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
[GET_TAG_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }), [GET_TAG_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),

View file

@ -1,11 +1,13 @@
import { Action } from 'redux'; import { Action } from 'redux';
import { ShortUrl } from '../../short-urls/data'; import { ShortUrl } from '../../short-urls/data';
import { ProblemDetailsError } from '../../utils/services/types';
export interface VisitsInfo { export interface VisitsInfo {
visits: Visit[]; visits: Visit[];
loading: boolean; loading: boolean;
loadingLarge: boolean; loadingLarge: boolean;
error: boolean; error: boolean;
errorData?: ProblemDetailsError;
progress: number; progress: number;
cancelLoad: boolean; cancelLoad: boolean;
} }
@ -14,6 +16,10 @@ export interface VisitsLoadProgressChangedAction extends Action<string> {
progress: number; progress: number;
} }
export interface VisitsLoadFailedAction extends Action<string> {
errorData?: ProblemDetailsError;
}
interface VisitLocation { interface VisitLocation {
countryCode: string | null; countryCode: string | null;
countryName: string | null; countryName: string | null;

View file

@ -103,7 +103,7 @@ describe('shortUrlVisitsReducer', () => {
beforeEach(() => dispatchMock.mockReset()); beforeEach(() => dispatchMock.mockReset());
it('dispatches start and error when promise is rejected', async () => { it('dispatches start and error when promise is rejected', async () => {
const ShlinkApiClient = buildApiClientMock(Promise.reject() as any); const ShlinkApiClient = buildApiClientMock(Promise.reject({}));
await getShortUrlVisits(() => ShlinkApiClient)('abc123')(dispatchMock, getState); await getShortUrlVisits(() => ShlinkApiClient)('abc123')(dispatchMock, getState);

View file

@ -103,7 +103,7 @@ describe('tagVisitsReducer', () => {
beforeEach(jest.resetAllMocks); beforeEach(jest.resetAllMocks);
it('dispatches start and error when promise is rejected', async () => { it('dispatches start and error when promise is rejected', async () => {
const ShlinkApiClient = buildApiClientMock(Promise.reject()); const ShlinkApiClient = buildApiClientMock(Promise.reject({}));
await getTagVisits(() => ShlinkApiClient)('foo')(dispatchMock, getState); await getTagVisits(() => ShlinkApiClient)('foo')(dispatchMock, getState);