diff --git a/.eslintrc b/.eslintrc index b14ea6b0..3a282c9c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,6 +28,7 @@ "no-warning-comments": "off", "no-magic-numbers": "off", "no-undefined": "off", + "no-inline-comments": "off", "indent": ["error", 2, { "SwitchCase": 1 } diff --git a/src/common/ErrorHandler.js b/src/common/ErrorHandler.js index 6dc0af26..96daf12a 100644 --- a/src/common/ErrorHandler.js +++ b/src/common/ErrorHandler.js @@ -3,6 +3,7 @@ import * as PropTypes from 'prop-types'; import './ErrorHandler.scss'; import { Button } from 'reactstrap'; +// FIXME Replace with typescript: (window, console) const ErrorHandler = ({ location }, { error }) => class ErrorHandler extends React.Component { static propTypes = { children: PropTypes.node.isRequired, diff --git a/src/short-urls/helpers/DeleteShortUrlModal.js b/src/short-urls/helpers/DeleteShortUrlModal.js index 1d2f0cc0..0a50d53f 100644 --- a/src/short-urls/helpers/DeleteShortUrlModal.js +++ b/src/short-urls/helpers/DeleteShortUrlModal.js @@ -5,6 +5,8 @@ import { identity } from 'ramda'; import { shortUrlType } from '../reducers/shortUrlsList'; import { shortUrlDeletionType } from '../reducers/shortUrlDeletion'; +const THRESHOLD_REACHED = 'INVALID_SHORTCODE_DELETION'; + export default class DeleteShortUrlModal extends React.Component { static propTypes = { shortUrl: shortUrlType, @@ -39,9 +41,10 @@ export default class DeleteShortUrlModal extends React.Component { render() { const { shortUrl, toggle, isOpen, shortUrlDeletion } = this.props; - const THRESHOLD_REACHED = 'INVALID_SHORTCODE_DELETION'; - const hasThresholdError = shortUrlDeletion.error && shortUrlDeletion.errorData.error === THRESHOLD_REACHED; - const hasErrorOtherThanThreshold = shortUrlDeletion.error && shortUrlDeletion.errorData.error !== THRESHOLD_REACHED; + const { error, errorData } = shortUrlDeletion; + const errorCode = error && (errorData.type || errorData.error); + const hasThresholdError = errorCode === THRESHOLD_REACHED; + const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED; return ( @@ -63,7 +66,8 @@ export default class DeleteShortUrlModal extends React.Component { {hasThresholdError && (
- This short URL has received too many visits and therefore, it cannot be deleted + {errorData.threshold && `This short URL has received more than ${errorData.threshold} visits, and therefore, it cannot be deleted.`} + {!errorData.threshold && 'This short URL has received too many visits, and therefore, it cannot be deleted.'}
)} {hasErrorOtherThanThreshold && ( diff --git a/src/short-urls/reducers/shortUrlDeletion.js b/src/short-urls/reducers/shortUrlDeletion.js index 62d63208..6e57b607 100644 --- a/src/short-urls/reducers/shortUrlDeletion.js +++ b/src/short-urls/reducers/shortUrlDeletion.js @@ -1,5 +1,6 @@ import { createAction, handleActions } from 'redux-actions'; import PropTypes from 'prop-types'; +import { apiErrorType } from '../../utils/services/ShlinkApiClient'; /* eslint-disable padding-line-between-statements */ export const DELETE_SHORT_URL_START = 'shlink/deleteShortUrl/DELETE_SHORT_URL_START'; @@ -13,10 +14,7 @@ export const shortUrlDeletionType = PropTypes.shape({ shortCode: PropTypes.string.isRequired, loading: PropTypes.bool.isRequired, error: PropTypes.bool.isRequired, - errorData: PropTypes.shape({ - error: PropTypes.string, - message: PropTypes.string, - }).isRequired, + errorData: apiErrorType.isRequired, }); const initialState = { diff --git a/src/utils/services/ShlinkApiClient.js b/src/utils/services/ShlinkApiClient.js index ac0b6fbf..a8e4a6ad 100644 --- a/src/utils/services/ShlinkApiClient.js +++ b/src/utils/services/ShlinkApiClient.js @@ -1,8 +1,18 @@ import qs from 'qs'; import { isEmpty, isNil, reject } from 'ramda'; +import PropTypes from 'prop-types'; const API_VERSION = '1'; +export const apiErrorType = PropTypes.shape({ + type: PropTypes.string, + detail: PropTypes.string, + title: PropTypes.string, + status: PropTypes.number, + error: PropTypes.string, // Deprecated + message: PropTypes.string, // Deprecated +}); + export const buildShlinkBaseUrl = (url) => url ? `${url}/rest/v${API_VERSION}` : ''; export default class ShlinkApiClient { diff --git a/test/short-urls/helpers/DeleteShortUrlModal.test.js b/test/short-urls/helpers/DeleteShortUrlModal.test.js index 204a2779..bfc48b65 100644 --- a/test/short-urls/helpers/DeleteShortUrlModal.test.js +++ b/test/short-urls/helpers/DeleteShortUrlModal.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { identity } from 'ramda'; +import each from 'jest-each'; import DeleteShortUrlModal from '../../../src/short-urls/helpers/DeleteShortUrlModal'; describe('', () => { @@ -32,17 +33,34 @@ describe('', () => { deleteShortUrl.mockClear(); }); - it('shows threshold error message when threshold error occurs', () => { + each([ + [ + { error: 'INVALID_SHORTCODE_DELETION' }, + 'This short URL has received too many visits, and therefore, it cannot be deleted.', + ], + [ + { type: 'INVALID_SHORTCODE_DELETION' }, + 'This short URL has received too many visits, and therefore, it cannot be deleted.', + ], + [ + { error: 'INVALID_SHORTCODE_DELETION', threshold: 35 }, + 'This short URL has received more than 35 visits, and therefore, it cannot be deleted.', + ], + [ + { type: 'INVALID_SHORTCODE_DELETION', threshold: 8 }, + 'This short URL has received more than 8 visits, and therefore, it cannot be deleted.', + ], + ]).it('shows threshold error message when threshold error occurs', (errorData, expectedMessage) => { const wrapper = createWrapper({ loading: false, error: true, shortCode: 'abc123', - errorData: { error: 'INVALID_SHORTCODE_DELETION' }, + errorData, }); const warning = wrapper.find('.bg-warning'); expect(warning).toHaveLength(1); - expect(warning.html()).toContain('This short URL has received too many visits and therefore, it cannot be deleted'); + expect(warning.html()).toContain(expectedMessage); }); it('shows generic error when non-threshold error occurs', () => {