mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-09 09:47:28 +03:00
Migrated short URL helper modal components to TS
This commit is contained in:
parent
b19bbee7fc
commit
f283dc8569
14 changed files with 90 additions and 130 deletions
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -27,3 +27,9 @@ export interface ShortUrlMeta {
|
||||||
validUntil?: string;
|
validUntil?: string;
|
||||||
maxVisits?: number;
|
maxVisits?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ShortUrlModalProps {
|
||||||
|
shortUrl: ShortUrl;
|
||||||
|
isOpen: boolean;
|
||||||
|
toggle: () => void;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
Loading…
Reference in a new issue