Migrated short URL helper modal components to TS

This commit is contained in:
Alejandro Celaya 2020-08-26 20:37:36 +02:00
parent b19bbee7fc
commit f283dc8569
14 changed files with 90 additions and 130 deletions

View file

@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { HorizontalFormGroup } from '../../utils/HorizontalFormGroup'; import { HorizontalFormGroup } from '../../utils/HorizontalFormGroup';
import { handleEventPreventingDefault } from '../../utils/utils';
const propTypes = { const propTypes = {
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
@ -16,10 +17,7 @@ export const ServerForm = ({ onSubmit, initialValues, children }) => {
const [ name, setName ] = useState(''); const [ name, setName ] = useState('');
const [ url, setUrl ] = useState(''); const [ url, setUrl ] = useState('');
const [ apiKey, setApiKey ] = useState(''); const [ apiKey, setApiKey ] = useState('');
const handleSubmit = (e) => { const handleSubmit = handleEventPreventingDefault(() => onSubmit({ name, url, apiKey }));
e.preventDefault();
onSubmit({ name, url, apiKey });
};
useEffect(() => { useEffect(() => {
initialValues && setName(initialValues.name); initialValues && setName(initialValues.name);

View file

@ -8,7 +8,7 @@ import DateInput from '../utils/DateInput';
import Checkbox from '../utils/Checkbox'; import Checkbox from '../utils/Checkbox';
import { serverType } from '../servers/prop-types'; import { serverType } from '../servers/prop-types';
import { versionMatch } from '../utils/helpers/version'; import { versionMatch } from '../utils/helpers/version';
import { hasValue } from '../utils/utils'; import { handleEventPreventingDefault, hasValue } from '../utils/utils';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { createShortUrlResultType } from './reducers/shortUrlCreation'; import { createShortUrlResultType } from './reducers/shortUrlCreation';
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon'; import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
@ -42,9 +42,7 @@ const CreateShortUrl = (TagsSelector, CreateShortUrlResult, ForServerVersion) =>
const changeTags = (tags) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) }); const changeTags = (tags) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) });
const reset = () => setShortUrlCreation(initialState); const reset = () => setShortUrlCreation(initialState);
const save = (e) => { const save = handleEventPreventingDefault(() => {
e.preventDefault();
const shortUrlData = { const shortUrlData = {
...shortUrlCreation, ...shortUrlCreation,
validSince: formatDate(shortUrlCreation.validSince), validSince: formatDate(shortUrlCreation.validSince),
@ -52,7 +50,7 @@ const CreateShortUrl = (TagsSelector, CreateShortUrlResult, ForServerVersion) =>
}; };
createShortUrl(shortUrlData).then(reset).catch(() => {}); createShortUrl(shortUrlData).then(reset).catch(() => {});
}; });
const renderOptionalInput = (id, placeholder, type = 'text', props = {}) => ( const renderOptionalInput = (id, placeholder, type = 'text', props = {}) => (
<FormGroup> <FormGroup>
<Input <Input

View file

@ -27,3 +27,9 @@ export interface ShortUrlMeta {
validUntil?: string; validUntil?: string;
maxVisits?: number; maxVisits?: number;
} }
export interface ShortUrlModalProps {
shortUrl: ShortUrl;
isOpen: boolean;
toggle: () => void;
}

View file

@ -1,40 +1,37 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import PropTypes from 'prop-types';
import { identity, pipe } from 'ramda'; import { identity, pipe } from 'ramda';
import { shortUrlType } from '../reducers/shortUrlsList'; import { ShortUrlDeletion } from '../reducers/shortUrlDeletion';
import { shortUrlDeletionType } from '../reducers/shortUrlDeletion'; import { ShortUrlModalProps } from '../data';
import { handleEventPreventingDefault, OptionalString } from '../../utils/utils';
const THRESHOLD_REACHED = 'INVALID_SHORTCODE_DELETION'; const THRESHOLD_REACHED = 'INVALID_SHORTCODE_DELETION';
const propTypes = { interface DeleteShortUrlModalConnectProps extends ShortUrlModalProps {
shortUrl: shortUrlType, shortUrlDeletion: ShortUrlDeletion;
toggle: PropTypes.func, deleteShortUrl: (shortCode: string, domain: OptionalString) => Promise<void>;
isOpen: PropTypes.bool, resetDeleteShortUrl: () => void;
shortUrlDeletion: shortUrlDeletionType, }
deleteShortUrl: PropTypes.func,
resetDeleteShortUrl: PropTypes.func,
};
const DeleteShortUrlModal = ({ shortUrl, toggle, isOpen, shortUrlDeletion, resetDeleteShortUrl, deleteShortUrl }) => { const DeleteShortUrlModal = (
{ shortUrl, toggle, isOpen, shortUrlDeletion, resetDeleteShortUrl, deleteShortUrl }: DeleteShortUrlModalConnectProps,
) => {
const [ inputValue, setInputValue ] = useState(''); const [ inputValue, setInputValue ] = useState('');
useEffect(() => resetDeleteShortUrl, []); useEffect(() => resetDeleteShortUrl, []);
const { error, errorData } = shortUrlDeletion; const { error, errorData } = shortUrlDeletion;
const errorCode = error && errorData && (errorData.type || errorData.error); const errorCode = error && (errorData?.type || errorData?.error);
const hasThresholdError = errorCode === THRESHOLD_REACHED; const hasThresholdError = errorCode === THRESHOLD_REACHED;
const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED; const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED;
const close = pipe(resetDeleteShortUrl, toggle); const close = pipe(resetDeleteShortUrl, toggle);
const handleDeleteUrl = (e) => { const handleDeleteUrl = handleEventPreventingDefault(() => {
e.preventDefault();
const { shortCode, domain } = shortUrl; const { shortCode, domain } = shortUrl;
deleteShortUrl(shortCode, domain) deleteShortUrl(shortCode, domain)
.then(toggle) .then(toggle)
.catch(identity); .catch(identity);
}; });
return ( return (
<Modal isOpen={isOpen} toggle={close} centered> <Modal isOpen={isOpen} toggle={close} centered>
@ -56,8 +53,8 @@ const DeleteShortUrlModal = ({ shortUrl, toggle, isOpen, shortUrlDeletion, reset
{hasThresholdError && ( {hasThresholdError && (
<div className="p-2 mt-2 bg-warning text-center"> <div className="p-2 mt-2 bg-warning text-center">
{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 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.'} {!errorData?.threshold && 'This short URL has received too many visits, and therefore, it cannot be deleted.'}
</div> </div>
)} )}
{hasErrorOtherThanThreshold && ( {hasErrorOtherThanThreshold && (
@ -81,6 +78,4 @@ const DeleteShortUrlModal = ({ shortUrl, toggle, isOpen, shortUrlDeletion, reset
); );
}; };
DeleteShortUrlModal.propTypes = propTypes;
export default DeleteShortUrlModal; export default DeleteShortUrlModal;

View file

@ -1,4 +1,4 @@
import React, { ChangeEvent, SyntheticEvent, useState } from 'react'; import React, { ChangeEvent, useState } from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, UncontrolledTooltip } from 'reactstrap'; import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, UncontrolledTooltip } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
@ -8,16 +8,10 @@ import { isEmpty, pipe } from 'ramda';
import { ShortUrlMetaEdition } from '../reducers/shortUrlMeta'; import { ShortUrlMetaEdition } from '../reducers/shortUrlMeta';
import DateInput from '../../utils/DateInput'; import DateInput from '../../utils/DateInput';
import { formatIsoDate } from '../../utils/helpers/date'; import { formatIsoDate } from '../../utils/helpers/date';
import { ShortUrl, ShortUrlMeta } from '../data'; import { ShortUrl, ShortUrlMeta, ShortUrlModalProps } from '../data';
import { Nullable, OptionalString } from '../../utils/utils'; import { handleEventPreventingDefault, Nullable, OptionalString } from '../../utils/utils';
export interface EditMetaModalProps { interface EditMetaModalConnectProps extends ShortUrlModalProps {
shortUrl: ShortUrl;
isOpen: boolean;
toggle: () => void;
}
interface EditMetaModalConnectProps extends EditMetaModalProps {
shortUrlMeta: ShortUrlMetaEdition; shortUrlMeta: ShortUrlMetaEdition;
resetShortUrlMeta: () => void; resetShortUrlMeta: () => void;
editShortUrlMeta: (shortCode: string, domain: OptionalString, meta: Nullable<ShortUrlMeta>) => Promise<void>; editShortUrlMeta: (shortCode: string, domain: OptionalString, meta: Nullable<ShortUrlMeta>) => Promise<void>;
@ -54,7 +48,7 @@ const EditMetaModal = (
<p>If any of the params is not met, the URL will behave as if it was an invalid short URL.</p> <p>If any of the params is not met, the URL will behave as if it was an invalid short URL.</p>
</UncontrolledTooltip> </UncontrolledTooltip>
</ModalHeader> </ModalHeader>
<form onSubmit={pipe((e: SyntheticEvent) => e.preventDefault(), doEdit)}> <form onSubmit={handleEventPreventingDefault(doEdit)}>
<ModalBody> <ModalBody>
<FormGroup> <FormGroup>
<DateInput <DateInput

View file

@ -1,32 +1,28 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, Button } from 'reactstrap'; import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, Button } from 'reactstrap';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import { shortUrlType } from '../reducers/shortUrlsList'; import { ShortUrlEdition } from '../reducers/shortUrlEdition';
import { ShortUrlEditionType } from '../reducers/shortUrlEdition'; import { handleEventPreventingDefault, hasValue, OptionalString } from '../../utils/utils';
import { hasValue } from '../../utils/utils'; import { ShortUrlModalProps } from '../data';
const propTypes = { interface EditShortUrlModalProps extends ShortUrlModalProps {
isOpen: PropTypes.bool.isRequired, shortUrlEdition: ShortUrlEdition;
toggle: PropTypes.func.isRequired, editShortUrl: (shortUrl: string, domain: OptionalString, longUrl: string) => Promise<void>;
shortUrl: shortUrlType.isRequired, }
shortUrlEdition: ShortUrlEditionType,
editShortUrl: PropTypes.func,
};
const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShortUrl }) => { const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShortUrl }: EditShortUrlModalProps) => {
const { saving, error } = shortUrlEdition; const { saving, error } = shortUrlEdition;
const url = shortUrl && (shortUrl.shortUrl || ''); const url = shortUrl?.shortUrl ?? '';
const [ longUrl, setLongUrl ] = useState(shortUrl.longUrl); const [ longUrl, setLongUrl ] = useState(shortUrl.longUrl);
const doEdit = () => editShortUrl(shortUrl.shortCode, shortUrl.domain, longUrl).then(toggle); const doEdit = async () => editShortUrl(shortUrl.shortCode, shortUrl.domain, longUrl).then(toggle);
return ( return (
<Modal isOpen={isOpen} toggle={toggle} centered> <Modal isOpen={isOpen} toggle={toggle} centered>
<ModalHeader toggle={toggle}> <ModalHeader toggle={toggle}>
Edit long URL for <ExternalLink href={url} /> Edit long URL for <ExternalLink href={url} />
</ModalHeader> </ModalHeader>
<form onSubmit={(e) => e.preventDefault() || doEdit()}> <form onSubmit={handleEventPreventingDefault(doEdit)}>
<ModalBody> <ModalBody>
<FormGroup className="mb-0"> <FormGroup className="mb-0">
<Input <Input
@ -52,6 +48,4 @@ const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShor
); );
}; };
EditShortUrlModal.propTypes = propTypes;
export default EditShortUrlModal; export default EditShortUrlModal;

View file

@ -1,6 +1,4 @@
import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { apiErrorType } from '../../utils/services/ShlinkApiClient';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux'; import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
import { ProblemDetailsError, ShlinkApiClientBuilder } from '../../utils/services/types'; import { ProblemDetailsError, ShlinkApiClientBuilder } from '../../utils/services/types';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
@ -12,14 +10,6 @@ export const SHORT_URL_DELETED = 'shlink/deleteShortUrl/SHORT_URL_DELETED';
export const RESET_DELETE_SHORT_URL = 'shlink/deleteShortUrl/RESET_DELETE_SHORT_URL'; export const RESET_DELETE_SHORT_URL = 'shlink/deleteShortUrl/RESET_DELETE_SHORT_URL';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
/** @deprecated Use ShortUrlDeletion interface */
export const shortUrlDeletionType = PropTypes.shape({
shortCode: PropTypes.string.isRequired,
loading: PropTypes.bool.isRequired,
error: PropTypes.bool.isRequired,
errorData: apiErrorType.isRequired,
});
export interface ShortUrlDeletion { export interface ShortUrlDeletion {
shortCode: string; shortCode: string;
loading: boolean; loading: boolean;

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { buildReducer } from '../../utils/helpers/redux'; import { buildReducer } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../utils/services/types'; import { ShlinkApiClientBuilder } from '../../utils/services/types';
@ -11,14 +10,6 @@ export const EDIT_SHORT_URL_ERROR = 'shlink/shortUrlEdition/EDIT_SHORT_URL_ERROR
export const SHORT_URL_EDITED = 'shlink/shortUrlEdition/SHORT_URL_EDITED'; export const SHORT_URL_EDITED = 'shlink/shortUrlEdition/SHORT_URL_EDITED';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
/** @deprecated Use ShortUrlEdition interface instead */
export const ShortUrlEditionType = PropTypes.shape({
shortCode: PropTypes.string,
longUrl: PropTypes.string,
saving: PropTypes.bool.isRequired,
error: PropTypes.bool.isRequired,
});
export interface ShortUrlEdition { export interface ShortUrlEdition {
shortCode: string | null; shortCode: string | null;
longUrl: string | null; longUrl: string | null;

View file

@ -6,6 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './EditTagModal.scss'; import './EditTagModal.scss';
import { useToggle } from '../../utils/helpers/hooks'; import { useToggle } from '../../utils/helpers/hooks';
import { handleEventPreventingDefault } from '../../utils/utils';
const propTypes = { const propTypes = {
tag: PropTypes.string, tag: PropTypes.string,
@ -24,14 +25,10 @@ const EditTagModal = ({ getColorForKey }) => {
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 ] = useToggle(); const [ showColorPicker, toggleColorPicker ] = useToggle();
const saveTag = (e) => { const saveTag = handleEventPreventingDefault(() => editTag(tag, newTagName, color)
e.preventDefault(); .then(() => tagEdited(tag, newTagName, color))
.then(toggle)
editTag(tag, newTagName, color) .catch(() => {}));
.then(() => tagEdited(tag, newTagName, color))
.then(toggle)
.catch(() => {});
};
return ( return (
<Modal isOpen={isOpen} toggle={toggle} centered> <Modal isOpen={isOpen} toggle={toggle} centered>

View file

@ -1,15 +1,5 @@
import qs from 'qs'; import qs from 'qs';
import { isEmpty, isNil, reject } from 'ramda'; import { isEmpty, isNil, reject } from 'ramda';
import PropTypes from 'prop-types';
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
});
const buildShlinkBaseUrl = (url, apiVersion) => url ? `${url}/rest/v${apiVersion}` : ''; const buildShlinkBaseUrl = (url, apiVersion) => url ? `${url}/rest/v${apiVersion}` : '';
const rejectNilProps = reject(isNil); const rejectNilProps = reject(isNil);

View file

@ -22,4 +22,5 @@ export interface ProblemDetailsError {
status: number; status: number;
error?: string; // Deprecated error?: string; // Deprecated
message?: string; // Deprecated message?: string; // Deprecated
[extraProps: string]: any;
} }

View file

@ -1,4 +1,5 @@
import { isEmpty, isNil, range } from 'ramda'; import { isEmpty, isNil, pipe, range } from 'ramda';
import { SyntheticEvent } from 'react';
export type OrderDir = 'ASC' | 'DESC' | undefined; export type OrderDir = 'ASC' | 'DESC' | undefined;
@ -22,6 +23,11 @@ export type Empty = null | undefined | '' | never[];
export const hasValue = <T>(value: T | Empty): value is T => !isNil(value) && !isEmpty(value); export const hasValue = <T>(value: T | Empty): value is T => !isNil(value) && !isEmpty(value);
export const handleEventPreventingDefault = <T>(handler: () => T) => pipe(
(e: SyntheticEvent) => e.preventDefault(),
handler,
);
export type Nullable<T> = { export type Nullable<T> = {
[P in keyof T]: T[P] | null [P in keyof T]: T[P] | null
}; };

View file

@ -1,35 +1,37 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow, ShallowWrapper } from 'enzyme';
import { identity } from 'ramda'; import { identity } from 'ramda';
import { Mock } from 'ts-mockery';
import DeleteShortUrlModal from '../../../src/short-urls/helpers/DeleteShortUrlModal'; import DeleteShortUrlModal from '../../../src/short-urls/helpers/DeleteShortUrlModal';
import { ShortUrl } from '../../../src/short-urls/data';
import { ShortUrlDeletion } from '../../../src/short-urls/reducers/shortUrlDeletion';
import { ProblemDetailsError } from '../../../src/utils/services/types';
describe('<DeleteShortUrlModal />', () => { describe('<DeleteShortUrlModal />', () => {
let wrapper; let wrapper: ShallowWrapper;
const shortUrl = { const shortUrl = Mock.of<ShortUrl>({
tags: [], tags: [],
shortCode: 'abc123', shortCode: 'abc123',
originalUrl: 'https://long-domain.com/foo/bar', longUrl: 'https://long-domain.com/foo/bar',
}; });
const deleteShortUrl = jest.fn(() => Promise.resolve()); const deleteShortUrl = jest.fn(async () => Promise.resolve());
const createWrapper = (shortUrlDeletion) => { const createWrapper = (shortUrlDeletion: Partial<ShortUrlDeletion>) => {
wrapper = shallow( wrapper = shallow(
<DeleteShortUrlModal <DeleteShortUrlModal
isOpen isOpen
shortUrl={shortUrl} shortUrl={shortUrl}
shortUrlDeletion={shortUrlDeletion} shortUrlDeletion={Mock.of<ShortUrlDeletion>(shortUrlDeletion)}
toggle={identity} toggle={() => {}}
deleteShortUrl={deleteShortUrl} deleteShortUrl={deleteShortUrl}
resetDeleteShortUrl={identity} resetDeleteShortUrl={() => {}}
/>, />,
); );
return wrapper; return wrapper;
}; };
afterEach(() => { afterEach(() => wrapper?.unmount());
wrapper && wrapper.unmount(); afterEach(jest.clearAllMocks);
deleteShortUrl.mockClear();
});
it.each([ it.each([
[ [
@ -48,12 +50,12 @@ describe('<DeleteShortUrlModal />', () => {
{ type: 'INVALID_SHORTCODE_DELETION', threshold: 8 }, { type: 'INVALID_SHORTCODE_DELETION', threshold: 8 },
'This short URL has received more than 8 visits, and therefore, it cannot be deleted.', 'This short URL has received more than 8 visits, and therefore, it cannot be deleted.',
], ],
])('shows threshold error message when threshold error occurs', (errorData, expectedMessage) => { ])('shows threshold error message when threshold error occurs', (errorData: Partial<ProblemDetailsError>, expectedMessage) => {
const wrapper = createWrapper({ const wrapper = createWrapper({
loading: false, loading: false,
error: true, error: true,
shortCode: 'abc123', shortCode: 'abc123',
errorData, errorData: Mock.of<ProblemDetailsError>(errorData),
}); });
const warning = wrapper.find('.bg-warning'); const warning = wrapper.find('.bg-warning');
@ -66,7 +68,7 @@ describe('<DeleteShortUrlModal />', () => {
loading: false, loading: false,
error: true, error: true,
shortCode: 'abc123', shortCode: 'abc123',
errorData: { error: 'OTHER_ERROR' }, errorData: Mock.of<ProblemDetailsError>({ error: 'OTHER_ERROR' }),
}); });
const error = wrapper.find('.bg-danger'); const error = wrapper.find('.bg-danger');
@ -79,7 +81,6 @@ describe('<DeleteShortUrlModal />', () => {
loading: true, loading: true,
error: false, error: false,
shortCode: 'abc123', shortCode: 'abc123',
errorData: {},
}); });
const submit = wrapper.find('.btn-danger'); const submit = wrapper.find('.btn-danger');
@ -94,7 +95,6 @@ describe('<DeleteShortUrlModal />', () => {
loading: false, loading: false,
error: false, error: false,
shortCode, shortCode,
errorData: {},
}); });
const input = wrapper.find('.form-control'); const input = wrapper.find('.form-control');
@ -113,7 +113,6 @@ describe('<DeleteShortUrlModal />', () => {
loading: false, loading: false,
error: false, error: false,
shortCode, shortCode,
errorData: {},
}); });
const input = wrapper.find('.form-control'); const input = wrapper.find('.form-control');

View file

@ -1,18 +1,21 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow, ShallowWrapper } from 'enzyme';
import { FormGroup, Modal, ModalHeader } from 'reactstrap'; import { FormGroup } from 'reactstrap';
import { Mock } from 'ts-mockery';
import EditShortUrlModal from '../../../src/short-urls/helpers/EditShortUrlModal'; import EditShortUrlModal from '../../../src/short-urls/helpers/EditShortUrlModal';
import { ShortUrl } from '../../../src/short-urls/data';
import { ShortUrlEdition } from '../../../src/short-urls/reducers/shortUrlEdition';
describe('<EditShortUrlModal />', () => { describe('<EditShortUrlModal />', () => {
let wrapper; let wrapper: ShallowWrapper;
const editShortUrl = jest.fn(() => Promise.resolve()); const editShortUrl = jest.fn(async () => Promise.resolve());
const toggle = jest.fn(); const toggle = jest.fn();
const createWrapper = (shortUrl, shortUrlEdition) => { const createWrapper = (shortUrl: Partial<ShortUrl>, shortUrlEdition: Partial<ShortUrlEdition>) => {
wrapper = shallow( wrapper = shallow(
<EditShortUrlModal <EditShortUrlModal
isOpen={true} isOpen={true}
shortUrl={shortUrl} shortUrl={Mock.of<ShortUrl>(shortUrl)}
shortUrlEdition={shortUrlEdition} shortUrlEdition={Mock.of<ShortUrlEdition>(shortUrlEdition)}
toggle={toggle} toggle={toggle}
editShortUrl={editShortUrl} editShortUrl={editShortUrl}
/>, />,
@ -21,10 +24,8 @@ describe('<EditShortUrlModal />', () => {
return wrapper; return wrapper;
}; };
afterEach(() => { afterEach(() => wrapper?.unmount());
wrapper && wrapper.unmount(); afterEach(jest.clearAllMocks);
jest.clearAllMocks();
});
it.each([ it.each([
[ false, 0 ], [ false, 0 ],
@ -66,13 +67,13 @@ describe('<EditShortUrlModal />', () => {
it.each([ it.each([
[ '[color="link"]', 'onClick' ], [ '[color="link"]', 'onClick' ],
[ Modal, 'toggle' ], [ 'Modal', 'toggle' ],
[ ModalHeader, 'toggle' ], [ 'ModalHeader', 'toggle' ],
])('toggles modal with different mechanisms', (componentToFind, propToCall) => { ])('toggles modal with different mechanisms', (componentToFind, propToCall) => {
const wrapper = createWrapper({}, { saving: false, error: false }); const wrapper = createWrapper({}, { saving: false, error: false });
const component = wrapper.find(componentToFind); const component = wrapper.find(componentToFind);
component.prop(propToCall)(); (component.prop(propToCall) as Function)(); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
expect(toggle).toHaveBeenCalled(); expect(toggle).toHaveBeenCalled();
}); });