mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Deleted modals that were used to edit short URLs, since now there's a dedicated section
This commit is contained in:
parent
1403538660
commit
3ad0c4d009
9 changed files with 4 additions and 561 deletions
|
@ -1,101 +0,0 @@
|
||||||
import { ChangeEvent, useState } from 'react';
|
|
||||||
import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, UncontrolledTooltip } from 'reactstrap';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { ExternalLink } from 'react-external-link';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { isEmpty, pipe } from 'ramda';
|
|
||||||
import { ShortUrlMetaEdition } from '../reducers/shortUrlMeta';
|
|
||||||
import DateInput from '../../utils/DateInput';
|
|
||||||
import { formatIsoDate } from '../../utils/helpers/date';
|
|
||||||
import { ShortUrl, ShortUrlMeta, ShortUrlModalProps } from '../data';
|
|
||||||
import { handleEventPreventingDefault, Nullable, OptionalString } from '../../utils/utils';
|
|
||||||
import { Result } from '../../utils/Result';
|
|
||||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
|
||||||
|
|
||||||
interface EditMetaModalConnectProps extends ShortUrlModalProps {
|
|
||||||
shortUrlMeta: ShortUrlMetaEdition;
|
|
||||||
resetShortUrlMeta: () => void;
|
|
||||||
editShortUrlMeta: (shortCode: string, domain: OptionalString, meta: Nullable<ShortUrlMeta>) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateOrNull = (shortUrl: ShortUrl | undefined, dateName: 'validSince' | 'validUntil') => {
|
|
||||||
const date = shortUrl?.meta?.[dateName];
|
|
||||||
|
|
||||||
return date ? moment(date) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const EditMetaModal = (
|
|
||||||
{ isOpen, toggle, shortUrl, shortUrlMeta, editShortUrlMeta, resetShortUrlMeta }: EditMetaModalConnectProps,
|
|
||||||
) => {
|
|
||||||
const { saving, error, errorData } = shortUrlMeta;
|
|
||||||
const url = shortUrl && (shortUrl.shortUrl || '');
|
|
||||||
const [ validSince, setValidSince ] = useState(dateOrNull(shortUrl, 'validSince'));
|
|
||||||
const [ validUntil, setValidUntil ] = useState(dateOrNull(shortUrl, 'validUntil'));
|
|
||||||
const [ maxVisits, setMaxVisits ] = useState(shortUrl?.meta?.maxVisits);
|
|
||||||
|
|
||||||
const close = pipe(resetShortUrlMeta, toggle);
|
|
||||||
const doEdit = async () => editShortUrlMeta(shortUrl.shortCode, shortUrl.domain, {
|
|
||||||
maxVisits: maxVisits && !isEmpty(maxVisits) ? maxVisits : null,
|
|
||||||
validSince: validSince && formatIsoDate(validSince),
|
|
||||||
validUntil: validUntil && formatIsoDate(validUntil),
|
|
||||||
}).then(close);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal isOpen={isOpen} toggle={close} centered>
|
|
||||||
<ModalHeader toggle={close}>
|
|
||||||
<FontAwesomeIcon icon={infoIcon} id="metaTitleInfo" /> Edit metadata for <ExternalLink href={url} />
|
|
||||||
<UncontrolledTooltip target="metaTitleInfo" placement="bottom">
|
|
||||||
<p>Using these metadata properties, you can limit when and how many times your short URL can be visited.</p>
|
|
||||||
<p>If any of the params is not met, the URL will behave as if it was an invalid short URL.</p>
|
|
||||||
</UncontrolledTooltip>
|
|
||||||
</ModalHeader>
|
|
||||||
<form onSubmit={handleEventPreventingDefault(doEdit)}>
|
|
||||||
<ModalBody>
|
|
||||||
<FormGroup>
|
|
||||||
<DateInput
|
|
||||||
placeholderText="Enabled since..."
|
|
||||||
selected={validSince}
|
|
||||||
maxDate={validUntil}
|
|
||||||
isClearable
|
|
||||||
onChange={setValidSince}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup>
|
|
||||||
<DateInput
|
|
||||||
placeholderText="Enabled until..."
|
|
||||||
selected={validUntil}
|
|
||||||
minDate={validSince}
|
|
||||||
isClearable
|
|
||||||
onChange={setValidUntil as any}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup className="mb-0">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
placeholder="Maximum number of visits allowed"
|
|
||||||
min={1}
|
|
||||||
value={maxVisits ?? ''}
|
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) => setMaxVisits(Number(e.target.value))}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<Result type="error" small className="mt-2">
|
|
||||||
<ShlinkApiError
|
|
||||||
errorData={errorData}
|
|
||||||
fallbackMessage="Something went wrong while saving the metadata :("
|
|
||||||
/>
|
|
||||||
</Result>
|
|
||||||
)}
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<button className="btn btn-link" type="button" onClick={close}>Cancel</button>
|
|
||||||
<button className="btn btn-primary" type="submit" disabled={saving}>{saving ? 'Saving...' : 'Save'}</button>
|
|
||||||
</ModalFooter>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditMetaModal;
|
|
|
@ -1,56 +0,0 @@
|
||||||
import { useState } from 'react';
|
|
||||||
import { Modal, ModalBody, ModalFooter, ModalHeader, FormGroup, Input, Button } from 'reactstrap';
|
|
||||||
import { ExternalLink } from 'react-external-link';
|
|
||||||
import { ShortUrlEdition } from '../reducers/shortUrlEdition';
|
|
||||||
import { handleEventPreventingDefault, hasValue, OptionalString } from '../../utils/utils';
|
|
||||||
import { EditShortUrlData, ShortUrlModalProps } from '../data';
|
|
||||||
import { Result } from '../../utils/Result';
|
|
||||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
|
||||||
|
|
||||||
interface EditShortUrlModalProps extends ShortUrlModalProps {
|
|
||||||
shortUrlEdition: ShortUrlEdition;
|
|
||||||
editShortUrl: (shortUrl: string, domain: OptionalString, data: EditShortUrlData) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShortUrl }: EditShortUrlModalProps) => {
|
|
||||||
const { saving, error, errorData } = shortUrlEdition;
|
|
||||||
const url = shortUrl?.shortUrl ?? '';
|
|
||||||
const [ longUrl, setLongUrl ] = useState(shortUrl.longUrl);
|
|
||||||
|
|
||||||
const doEdit = async () => editShortUrl(shortUrl.shortCode, shortUrl.domain, { longUrl }).then(toggle);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal isOpen={isOpen} toggle={toggle} centered size="lg">
|
|
||||||
<ModalHeader toggle={toggle}>
|
|
||||||
Edit long URL for <ExternalLink href={url} />
|
|
||||||
</ModalHeader>
|
|
||||||
<form onSubmit={handleEventPreventingDefault(doEdit)}>
|
|
||||||
<ModalBody>
|
|
||||||
<FormGroup className="mb-0">
|
|
||||||
<Input
|
|
||||||
type="url"
|
|
||||||
required
|
|
||||||
placeholder="Long URL"
|
|
||||||
value={longUrl}
|
|
||||||
onChange={(e) => setLongUrl(e.target.value)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
{error && (
|
|
||||||
<Result type="error" small className="mt-2">
|
|
||||||
<ShlinkApiError
|
|
||||||
errorData={errorData}
|
|
||||||
fallbackMessage="Something went wrong while saving the long URL :("
|
|
||||||
/>
|
|
||||||
</Result>
|
|
||||||
)}
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button color="link" onClick={toggle}>Cancel</Button>
|
|
||||||
<Button color="primary" disabled={saving || !hasValue(longUrl)}>{saving ? 'Saving...' : 'Save'}</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditShortUrlModal;
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { FC, useEffect, useState } from 'react';
|
|
||||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
|
||||||
import { ExternalLink } from 'react-external-link';
|
|
||||||
import { ShortUrlTags } from '../reducers/shortUrlTags';
|
|
||||||
import { ShortUrlModalProps } from '../data';
|
|
||||||
import { OptionalString } from '../../utils/utils';
|
|
||||||
import { TagsSelectorProps } from '../../tags/helpers/TagsSelector';
|
|
||||||
import { Result } from '../../utils/Result';
|
|
||||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
|
||||||
|
|
||||||
interface EditTagsModalProps extends ShortUrlModalProps {
|
|
||||||
shortUrlTags: ShortUrlTags;
|
|
||||||
editShortUrlTags: (shortCode: string, domain: OptionalString, tags: string[]) => Promise<void>;
|
|
||||||
resetShortUrlsTags: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditTagsModal = (TagsSelector: FC<TagsSelectorProps>) => (
|
|
||||||
{ isOpen, toggle, shortUrl, shortUrlTags, editShortUrlTags, resetShortUrlsTags }: EditTagsModalProps,
|
|
||||||
) => {
|
|
||||||
const [ selectedTags, setSelectedTags ] = useState<string[]>(shortUrl.tags || []);
|
|
||||||
|
|
||||||
useEffect(() => resetShortUrlsTags, []);
|
|
||||||
|
|
||||||
const { saving, error, errorData } = shortUrlTags;
|
|
||||||
const url = shortUrl?.shortUrl ?? '';
|
|
||||||
const saveTags = async () => editShortUrlTags(shortUrl.shortCode, shortUrl.domain, selectedTags)
|
|
||||||
.then(toggle)
|
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal isOpen={isOpen} toggle={toggle} centered>
|
|
||||||
<ModalHeader toggle={toggle}>
|
|
||||||
Edit tags for <ExternalLink href={url} />
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<TagsSelector tags={selectedTags} onChange={setSelectedTags} />
|
|
||||||
{error && (
|
|
||||||
<Result type="error" small className="mt-2">
|
|
||||||
<ShlinkApiError errorData={errorData} fallbackMessage="Something went wrong while saving the tags :(" />
|
|
||||||
</Result>
|
|
||||||
)}
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<button className="btn btn-link" onClick={toggle}>Cancel</button>
|
|
||||||
<button className="btn btn-primary" type="button" disabled={saving} onClick={saveTags}>
|
|
||||||
{saving ? 'Saving tags...' : 'Save tags'}
|
|
||||||
</button>
|
|
||||||
</ModalFooter>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditTagsModal;
|
|
|
@ -1,18 +1,15 @@
|
||||||
import {
|
import {
|
||||||
faTags as tagsIcon,
|
|
||||||
faChartPie as pieChartIcon,
|
faChartPie as pieChartIcon,
|
||||||
faEllipsisV as menuIcon,
|
faEllipsisV as menuIcon,
|
||||||
faQrcode as qrIcon,
|
faQrcode as qrIcon,
|
||||||
faMinusCircle as deleteIcon,
|
faMinusCircle as deleteIcon,
|
||||||
faEdit as editIcon,
|
faEdit as editIcon,
|
||||||
faLink as linkIcon,
|
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||||
import { useToggle } from '../../utils/helpers/hooks';
|
import { useToggle } from '../../utils/helpers/hooks';
|
||||||
import { ShortUrl, ShortUrlModalProps } from '../data';
|
import { ShortUrl, ShortUrlModalProps } from '../data';
|
||||||
import { Versions } from '../../utils/helpers/version';
|
|
||||||
import { SelectedServer } from '../../servers/data';
|
import { SelectedServer } from '../../servers/data';
|
||||||
import ShortUrlDetailLink from './ShortUrlDetailLink';
|
import ShortUrlDetailLink from './ShortUrlDetailLink';
|
||||||
import './ShortUrlsRowMenu.scss';
|
import './ShortUrlsRowMenu.scss';
|
||||||
|
@ -25,18 +22,11 @@ type ShortUrlModal = FC<ShortUrlModalProps>;
|
||||||
|
|
||||||
const ShortUrlsRowMenu = (
|
const ShortUrlsRowMenu = (
|
||||||
DeleteShortUrlModal: ShortUrlModal,
|
DeleteShortUrlModal: ShortUrlModal,
|
||||||
EditTagsModal: ShortUrlModal,
|
|
||||||
EditMetaModal: ShortUrlModal,
|
|
||||||
EditShortUrlModal: ShortUrlModal,
|
|
||||||
QrCodeModal: ShortUrlModal,
|
QrCodeModal: ShortUrlModal,
|
||||||
ForServerVersion: FC<Versions>,
|
|
||||||
) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => {
|
) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => {
|
||||||
const [ isOpen, toggle ] = useToggle();
|
const [ isOpen, toggle ] = useToggle();
|
||||||
const [ isQrModalOpen, toggleQrCode ] = useToggle();
|
const [ isQrModalOpen, toggleQrCode ] = useToggle();
|
||||||
const [ isTagsModalOpen, toggleTags ] = useToggle();
|
|
||||||
const [ isMetaModalOpen, toggleMeta ] = useToggle();
|
|
||||||
const [ isDeleteModalOpen, toggleDelete ] = useToggle();
|
const [ isDeleteModalOpen, toggleDelete ] = useToggle();
|
||||||
const [ isEditModalOpen, toggleEdit ] = useToggle();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonDropdown toggle={toggle} isOpen={isOpen}>
|
<ButtonDropdown toggle={toggle} isOpen={isOpen}>
|
||||||
|
@ -52,23 +42,6 @@ const ShortUrlsRowMenu = (
|
||||||
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit short URL
|
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit short URL
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
||||||
<DropdownItem onClick={toggleTags}>
|
|
||||||
<FontAwesomeIcon icon={tagsIcon} fixedWidth /> Edit tags
|
|
||||||
</DropdownItem>
|
|
||||||
<EditTagsModal shortUrl={shortUrl} isOpen={isTagsModalOpen} toggle={toggleTags} />
|
|
||||||
|
|
||||||
<DropdownItem onClick={toggleMeta}>
|
|
||||||
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit metadata
|
|
||||||
</DropdownItem>
|
|
||||||
<EditMetaModal shortUrl={shortUrl} isOpen={isMetaModalOpen} toggle={toggleMeta} />
|
|
||||||
|
|
||||||
<ForServerVersion minVersion="2.1.0">
|
|
||||||
<DropdownItem onClick={toggleEdit}>
|
|
||||||
<FontAwesomeIcon icon={linkIcon} fixedWidth /> Edit long URL
|
|
||||||
</DropdownItem>
|
|
||||||
<EditShortUrlModal shortUrl={shortUrl} isOpen={isEditModalOpen} toggle={toggleEdit} />
|
|
||||||
</ForServerVersion>
|
|
||||||
|
|
||||||
<DropdownItem onClick={toggleQrCode}>
|
<DropdownItem onClick={toggleQrCode}>
|
||||||
<FontAwesomeIcon icon={qrIcon} fixedWidth /> QR code
|
<FontAwesomeIcon icon={qrIcon} fixedWidth /> QR code
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
|
@ -6,9 +6,6 @@ import ShortUrlsRow from '../helpers/ShortUrlsRow';
|
||||||
import ShortUrlsRowMenu from '../helpers/ShortUrlsRowMenu';
|
import ShortUrlsRowMenu from '../helpers/ShortUrlsRowMenu';
|
||||||
import CreateShortUrl from '../CreateShortUrl';
|
import CreateShortUrl from '../CreateShortUrl';
|
||||||
import DeleteShortUrlModal from '../helpers/DeleteShortUrlModal';
|
import DeleteShortUrlModal from '../helpers/DeleteShortUrlModal';
|
||||||
import EditTagsModal from '../helpers/EditTagsModal';
|
|
||||||
import EditMetaModal from '../helpers/EditMetaModal';
|
|
||||||
import EditShortUrlModal from '../helpers/EditShortUrlModal';
|
|
||||||
import CreateShortUrlResult from '../helpers/CreateShortUrlResult';
|
import CreateShortUrlResult from '../helpers/CreateShortUrlResult';
|
||||||
import { listShortUrls } from '../reducers/shortUrlsList';
|
import { listShortUrls } from '../reducers/shortUrlsList';
|
||||||
import { createShortUrl, resetCreateShortUrl } from '../reducers/shortUrlCreation';
|
import { createShortUrl, resetCreateShortUrl } from '../reducers/shortUrlCreation';
|
||||||
|
@ -37,16 +34,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
|
|
||||||
bottle.serviceFactory('ShortUrlsTable', ShortUrlsTable, 'ShortUrlsRow');
|
bottle.serviceFactory('ShortUrlsTable', ShortUrlsTable, 'ShortUrlsRow');
|
||||||
bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator', 'useStateFlagTimeout');
|
bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator', 'useStateFlagTimeout');
|
||||||
bottle.serviceFactory(
|
bottle.serviceFactory('ShortUrlsRowMenu', ShortUrlsRowMenu, 'DeleteShortUrlModal', 'QrCodeModal');
|
||||||
'ShortUrlsRowMenu',
|
|
||||||
ShortUrlsRowMenu,
|
|
||||||
'DeleteShortUrlModal',
|
|
||||||
'EditTagsModal',
|
|
||||||
'EditMetaModal',
|
|
||||||
'EditShortUrlModal',
|
|
||||||
'QrCodeModal',
|
|
||||||
'ForServerVersion',
|
|
||||||
);
|
|
||||||
bottle.serviceFactory('CreateShortUrlResult', CreateShortUrlResult, 'useStateFlagTimeout');
|
bottle.serviceFactory('CreateShortUrlResult', CreateShortUrlResult, 'useStateFlagTimeout');
|
||||||
bottle.serviceFactory('ShortUrlForm', ShortUrlForm, 'TagsSelector', 'ForServerVersion', 'DomainSelector');
|
bottle.serviceFactory('ShortUrlForm', ShortUrlForm, 'TagsSelector', 'ForServerVersion', 'DomainSelector');
|
||||||
|
|
||||||
|
@ -65,15 +53,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
bottle.serviceFactory('DeleteShortUrlModal', () => DeleteShortUrlModal);
|
bottle.serviceFactory('DeleteShortUrlModal', () => DeleteShortUrlModal);
|
||||||
bottle.decorator('DeleteShortUrlModal', connect([ 'shortUrlDeletion' ], [ 'deleteShortUrl', 'resetDeleteShortUrl' ]));
|
bottle.decorator('DeleteShortUrlModal', connect([ 'shortUrlDeletion' ], [ 'deleteShortUrl', 'resetDeleteShortUrl' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('EditTagsModal', EditTagsModal, 'TagsSelector');
|
|
||||||
bottle.decorator('EditTagsModal', connect([ 'shortUrlTags' ], [ 'editShortUrlTags', 'resetShortUrlsTags' ]));
|
|
||||||
|
|
||||||
bottle.serviceFactory('EditMetaModal', () => EditMetaModal);
|
|
||||||
bottle.decorator('EditMetaModal', connect([ 'shortUrlMeta' ], [ 'editShortUrlMeta', 'resetShortUrlMeta' ]));
|
|
||||||
|
|
||||||
bottle.serviceFactory('EditShortUrlModal', () => EditShortUrlModal);
|
|
||||||
bottle.decorator('EditShortUrlModal', connect([ 'shortUrlEdition' ], [ 'editShortUrl' ]));
|
|
||||||
|
|
||||||
bottle.serviceFactory('QrCodeModal', () => QrCodeModal);
|
bottle.serviceFactory('QrCodeModal', () => QrCodeModal);
|
||||||
bottle.decorator('QrCodeModal', connect([ 'selectedServer' ]));
|
bottle.decorator('QrCodeModal', connect([ 'selectedServer' ]));
|
||||||
|
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
|
||||||
import { FormGroup } from 'reactstrap';
|
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import EditMetaModal from '../../../src/short-urls/helpers/EditMetaModal';
|
|
||||||
import { ShortUrl } from '../../../src/short-urls/data';
|
|
||||||
import { ShortUrlMetaEdition } from '../../../src/short-urls/reducers/shortUrlMeta';
|
|
||||||
import { Result } from '../../../src/utils/Result';
|
|
||||||
|
|
||||||
describe('<EditMetaModal />', () => {
|
|
||||||
let wrapper: ShallowWrapper;
|
|
||||||
const editShortUrlMeta = jest.fn(async () => Promise.resolve());
|
|
||||||
const resetShortUrlMeta = jest.fn();
|
|
||||||
const toggle = jest.fn();
|
|
||||||
const createWrapper = (shortUrlMeta: Partial<ShortUrlMetaEdition>) => {
|
|
||||||
wrapper = shallow(
|
|
||||||
<EditMetaModal
|
|
||||||
isOpen={true}
|
|
||||||
shortUrl={Mock.all<ShortUrl>()}
|
|
||||||
shortUrlMeta={Mock.of<ShortUrlMetaEdition>(shortUrlMeta)}
|
|
||||||
toggle={toggle}
|
|
||||||
editShortUrlMeta={editShortUrlMeta}
|
|
||||||
resetShortUrlMeta={resetShortUrlMeta}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
return wrapper;
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => wrapper?.unmount());
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('properly renders form with components', () => {
|
|
||||||
const wrapper = createWrapper({ saving: false, error: false });
|
|
||||||
const error = wrapper.find(Result).filterWhere((result) => result.prop('type') === 'error');
|
|
||||||
const form = wrapper.find('form');
|
|
||||||
const formGroup = form.find(FormGroup);
|
|
||||||
|
|
||||||
expect(form).toHaveLength(1);
|
|
||||||
expect(formGroup).toHaveLength(3);
|
|
||||||
expect(error).toHaveLength(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[ true, 'Saving...' ],
|
|
||||||
[ false, 'Save' ],
|
|
||||||
])('renders submit button on expected state', (saving, expectedText) => {
|
|
||||||
const wrapper = createWrapper({ saving, error: false });
|
|
||||||
const button = wrapper.find('[type="submit"]');
|
|
||||||
|
|
||||||
expect(button.prop('disabled')).toEqual(saving);
|
|
||||||
expect(button.text()).toContain(expectedText);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders error message on error', () => {
|
|
||||||
const wrapper = createWrapper({ saving: false, error: true });
|
|
||||||
const error = wrapper.find(Result).filterWhere((result) => result.prop('type') === 'error');
|
|
||||||
|
|
||||||
expect(error).toHaveLength(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('saves meta when form is submit', () => {
|
|
||||||
const preventDefault = jest.fn();
|
|
||||||
const wrapper = createWrapper({ saving: false, error: false });
|
|
||||||
const form = wrapper.find('form');
|
|
||||||
|
|
||||||
form.simulate('submit', { preventDefault });
|
|
||||||
|
|
||||||
expect(preventDefault).toHaveBeenCalled();
|
|
||||||
expect(editShortUrlMeta).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[ '.btn-link', 'onClick' ],
|
|
||||||
[ 'Modal', 'toggle' ],
|
|
||||||
[ 'ModalHeader', 'toggle' ],
|
|
||||||
])('resets meta when modal is toggled in any way', (componentToFind, propToCall) => {
|
|
||||||
const wrapper = createWrapper({ saving: false, error: false });
|
|
||||||
const component = wrapper.find(componentToFind);
|
|
||||||
|
|
||||||
(component.prop(propToCall) as Function)(); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
|
||||||
|
|
||||||
expect(resetShortUrlMeta).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,80 +0,0 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
|
||||||
import { FormGroup } from 'reactstrap';
|
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import EditShortUrlModal from '../../../src/short-urls/helpers/EditShortUrlModal';
|
|
||||||
import { ShortUrl } from '../../../src/short-urls/data';
|
|
||||||
import { ShortUrlEdition } from '../../../src/short-urls/reducers/shortUrlEdition';
|
|
||||||
import { Result } from '../../../src/utils/Result';
|
|
||||||
|
|
||||||
describe('<EditShortUrlModal />', () => {
|
|
||||||
let wrapper: ShallowWrapper;
|
|
||||||
const editShortUrl = jest.fn(async () => Promise.resolve());
|
|
||||||
const toggle = jest.fn();
|
|
||||||
const createWrapper = (shortUrl: Partial<ShortUrl>, shortUrlEdition: Partial<ShortUrlEdition>) => {
|
|
||||||
wrapper = shallow(
|
|
||||||
<EditShortUrlModal
|
|
||||||
isOpen={true}
|
|
||||||
shortUrl={Mock.of<ShortUrl>(shortUrl)}
|
|
||||||
shortUrlEdition={Mock.of<ShortUrlEdition>(shortUrlEdition)}
|
|
||||||
toggle={toggle}
|
|
||||||
editShortUrl={editShortUrl}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
return wrapper;
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => wrapper?.unmount());
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[ false, 0 ],
|
|
||||||
[ true, 1 ],
|
|
||||||
])('properly renders form with expected components', (error, expectedErrorLength) => {
|
|
||||||
const wrapper = createWrapper({}, { saving: false, error });
|
|
||||||
const errorElement = wrapper.find(Result).filterWhere((result) => result.prop('type') === 'error');
|
|
||||||
const form = wrapper.find('form');
|
|
||||||
const formGroup = form.find(FormGroup);
|
|
||||||
|
|
||||||
expect(form).toHaveLength(1);
|
|
||||||
expect(formGroup).toHaveLength(1);
|
|
||||||
expect(errorElement).toHaveLength(expectedErrorLength);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[ true, 'Saving...', 'something', true ],
|
|
||||||
[ true, 'Saving...', undefined, true ],
|
|
||||||
[ false, 'Save', 'something', false ],
|
|
||||||
[ false, 'Save', undefined, true ],
|
|
||||||
])('renders submit button on expected state', (saving, expectedText, longUrl, expectedDisabled) => {
|
|
||||||
const wrapper = createWrapper({ longUrl }, { saving, error: false });
|
|
||||||
const button = wrapper.find('[color="primary"]');
|
|
||||||
|
|
||||||
expect(button.prop('disabled')).toEqual(expectedDisabled);
|
|
||||||
expect(button.html()).toContain(expectedText);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('saves data when form is submit', () => {
|
|
||||||
const preventDefault = jest.fn();
|
|
||||||
const wrapper = createWrapper({}, { saving: false, error: false });
|
|
||||||
const form = wrapper.find('form');
|
|
||||||
|
|
||||||
form.simulate('submit', { preventDefault });
|
|
||||||
|
|
||||||
expect(preventDefault).toHaveBeenCalled();
|
|
||||||
expect(editShortUrl).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[ '[color="link"]', 'onClick' ],
|
|
||||||
[ 'Modal', 'toggle' ],
|
|
||||||
[ 'ModalHeader', 'toggle' ],
|
|
||||||
])('toggles modal with different mechanisms', (componentToFind, propToCall) => {
|
|
||||||
const wrapper = createWrapper({}, { saving: false, error: false });
|
|
||||||
const component = wrapper.find(componentToFind);
|
|
||||||
|
|
||||||
(component.prop(propToCall) as Function)(); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
|
||||||
|
|
||||||
expect(toggle).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,119 +0,0 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import { Modal } from 'reactstrap';
|
|
||||||
import createEditTagsModal from '../../../src/short-urls/helpers/EditTagsModal';
|
|
||||||
import { ShortUrl } from '../../../src/short-urls/data';
|
|
||||||
import { ShortUrlTags } from '../../../src/short-urls/reducers/shortUrlTags';
|
|
||||||
import { OptionalString } from '../../../src/utils/utils';
|
|
||||||
|
|
||||||
describe('<EditTagsModal />', () => {
|
|
||||||
let wrapper: ShallowWrapper;
|
|
||||||
const shortCode = 'abc123';
|
|
||||||
const TagsSelector = () => null;
|
|
||||||
const editShortUrlTags = jest.fn(async () => Promise.resolve());
|
|
||||||
const resetShortUrlsTags = jest.fn();
|
|
||||||
const toggle = jest.fn();
|
|
||||||
const createWrapper = (shortUrlTags: ShortUrlTags, domain?: OptionalString) => {
|
|
||||||
const EditTagsModal = createEditTagsModal(TagsSelector);
|
|
||||||
|
|
||||||
wrapper = shallow(
|
|
||||||
<EditTagsModal
|
|
||||||
isOpen={true}
|
|
||||||
shortUrl={Mock.of<ShortUrl>({
|
|
||||||
tags: [],
|
|
||||||
shortCode,
|
|
||||||
domain,
|
|
||||||
longUrl: 'https://long-domain.com/foo/bar',
|
|
||||||
})}
|
|
||||||
shortUrlTags={shortUrlTags}
|
|
||||||
toggle={toggle}
|
|
||||||
editShortUrlTags={editShortUrlTags}
|
|
||||||
resetShortUrlsTags={resetShortUrlsTags}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
return wrapper;
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => wrapper?.unmount());
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders tags selector and save button when loaded', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
shortCode,
|
|
||||||
tags: [],
|
|
||||||
saving: false,
|
|
||||||
error: false,
|
|
||||||
});
|
|
||||||
const saveBtn = wrapper.find('.btn-primary');
|
|
||||||
|
|
||||||
expect(wrapper.find(TagsSelector)).toHaveLength(1);
|
|
||||||
expect(saveBtn.prop('disabled')).toBe(false);
|
|
||||||
expect(saveBtn.text()).toEqual('Save tags');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('disables save button when saving is in progress', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
shortCode,
|
|
||||||
tags: [],
|
|
||||||
saving: true,
|
|
||||||
error: false,
|
|
||||||
});
|
|
||||||
const saveBtn = wrapper.find('.btn-primary');
|
|
||||||
|
|
||||||
expect(saveBtn.prop('disabled')).toBe(true);
|
|
||||||
expect(saveBtn.text()).toEqual('Saving tags...');
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[ undefined ],
|
|
||||||
[ null ],
|
|
||||||
[ 'example.com' ],
|
|
||||||
// @ts-expect-error Type declaration is not correct, which makes "done" function not being properly detected
|
|
||||||
])('saves tags when save button is clicked', (domain: OptionalString, done: jest.DoneCallback) => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
shortCode,
|
|
||||||
tags: [],
|
|
||||||
saving: true,
|
|
||||||
error: false,
|
|
||||||
}, domain);
|
|
||||||
const saveBtn = wrapper.find('.btn-primary');
|
|
||||||
|
|
||||||
saveBtn.simulate('click');
|
|
||||||
|
|
||||||
expect(editShortUrlTags).toHaveBeenCalledTimes(1);
|
|
||||||
expect(editShortUrlTags).toHaveBeenCalledWith(shortCode, domain, []);
|
|
||||||
|
|
||||||
// Wrap this expect in a setImmediate since it is called as a result of an inner promise
|
|
||||||
setImmediate(() => {
|
|
||||||
expect(toggle).toHaveBeenCalledTimes(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not notify tags have been edited when window is closed without saving', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
shortCode,
|
|
||||||
tags: [],
|
|
||||||
saving: false,
|
|
||||||
error: false,
|
|
||||||
});
|
|
||||||
const modal = wrapper.find(Modal);
|
|
||||||
|
|
||||||
modal.simulate('closed');
|
|
||||||
expect(editShortUrlTags).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('toggles modal when cancel button is clicked', () => {
|
|
||||||
const wrapper = createWrapper({
|
|
||||||
shortCode,
|
|
||||||
tags: [],
|
|
||||||
saving: true,
|
|
||||||
error: false,
|
|
||||||
});
|
|
||||||
const cancelBtn = wrapper.find('.btn-link');
|
|
||||||
|
|
||||||
cancelBtn.simulate('click');
|
|
||||||
expect(toggle).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -8,9 +8,6 @@ import { ShortUrl } from '../../../src/short-urls/data';
|
||||||
describe('<ShortUrlsRowMenu />', () => {
|
describe('<ShortUrlsRowMenu />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const DeleteShortUrlModal = () => null;
|
const DeleteShortUrlModal = () => null;
|
||||||
const EditTagsModal = () => null;
|
|
||||||
const EditMetaModal = () => null;
|
|
||||||
const EditShortUrlModal = () => null;
|
|
||||||
const QrCodeModal = () => null;
|
const QrCodeModal = () => null;
|
||||||
const selectedServer = Mock.of<ReachableServer>({ id: 'abc123' });
|
const selectedServer = Mock.of<ReachableServer>({ id: 'abc123' });
|
||||||
const shortUrl = Mock.of<ShortUrl>({
|
const shortUrl = Mock.of<ShortUrl>({
|
||||||
|
@ -18,14 +15,7 @@ describe('<ShortUrlsRowMenu />', () => {
|
||||||
shortUrl: 'https://doma.in/abc123',
|
shortUrl: 'https://doma.in/abc123',
|
||||||
});
|
});
|
||||||
const createWrapper = () => {
|
const createWrapper = () => {
|
||||||
const ShortUrlsRowMenu = createShortUrlsRowMenu(
|
const ShortUrlsRowMenu = createShortUrlsRowMenu(DeleteShortUrlModal, QrCodeModal);
|
||||||
DeleteShortUrlModal,
|
|
||||||
EditTagsModal,
|
|
||||||
EditMetaModal,
|
|
||||||
EditShortUrlModal,
|
|
||||||
QrCodeModal,
|
|
||||||
() => null,
|
|
||||||
);
|
|
||||||
|
|
||||||
wrapper = shallow(<ShortUrlsRowMenu selectedServer={selectedServer} shortUrl={shortUrl} />);
|
wrapper = shallow(<ShortUrlsRowMenu selectedServer={selectedServer} shortUrl={shortUrl} />);
|
||||||
|
|
||||||
|
@ -37,21 +27,17 @@ describe('<ShortUrlsRowMenu />', () => {
|
||||||
it('renders modal windows', () => {
|
it('renders modal windows', () => {
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
const deleteShortUrlModal = wrapper.find(DeleteShortUrlModal);
|
const deleteShortUrlModal = wrapper.find(DeleteShortUrlModal);
|
||||||
const editTagsModal = wrapper.find(EditTagsModal);
|
|
||||||
const qrCodeModal = wrapper.find(QrCodeModal);
|
const qrCodeModal = wrapper.find(QrCodeModal);
|
||||||
const editModal = wrapper.find(EditShortUrlModal);
|
|
||||||
|
|
||||||
expect(deleteShortUrlModal).toHaveLength(1);
|
expect(deleteShortUrlModal).toHaveLength(1);
|
||||||
expect(editTagsModal).toHaveLength(1);
|
|
||||||
expect(qrCodeModal).toHaveLength(1);
|
expect(qrCodeModal).toHaveLength(1);
|
||||||
expect(editModal).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correct amount of menu items', () => {
|
it('renders correct amount of menu items', () => {
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
const items = wrapper.find(DropdownItem);
|
const items = wrapper.find(DropdownItem);
|
||||||
|
|
||||||
expect(items).toHaveLength(8);
|
expect(items).toHaveLength(5);
|
||||||
expect(items.find('[divider]')).toHaveLength(1);
|
expect(items.find('[divider]')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -65,9 +51,7 @@ describe('<ShortUrlsRowMenu />', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it('DeleteShortUrlModal', () => assert(DeleteShortUrlModal));
|
it('DeleteShortUrlModal', () => assert(DeleteShortUrlModal));
|
||||||
it('EditTagsModal', () => assert(EditTagsModal));
|
|
||||||
it('QrCodeModal', () => assert(QrCodeModal));
|
it('QrCodeModal', () => assert(QrCodeModal));
|
||||||
it('EditShortUrlModal', () => assert(EditShortUrlModal));
|
it('ShortUrlRowMenu', () => assert(ButtonDropdown));
|
||||||
it('EditShortUrlModal', () => assert(ButtonDropdown));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue