mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-10 18:27:25 +03:00
Created view to edit short URLs
This commit is contained in:
parent
631b46393b
commit
a019bd30df
16 changed files with 190 additions and 61 deletions
|
@ -21,6 +21,7 @@ const MenuLayout = (
|
|||
OrphanVisits: FC,
|
||||
ServerError: FC,
|
||||
Overview: FC,
|
||||
EditShortUrl: FC,
|
||||
) => withSelectedServer(({ location, selectedServer }) => {
|
||||
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
|
||||
|
||||
|
@ -50,6 +51,7 @@ const MenuLayout = (
|
|||
<Route exact path="/server/:serverId/list-short-urls/:page" component={ShortUrls} />
|
||||
<Route exact path="/server/:serverId/create-short-url" component={CreateShortUrl} />
|
||||
<Route path="/server/:serverId/short-code/:shortCode/visits" component={ShortUrlVisits} />
|
||||
<Route path="/server/:serverId/short-code/:shortCode/edit" component={EditShortUrl} />
|
||||
{addTagsVisitsRoute && <Route path="/server/:serverId/tag/:tag/visits" component={TagVisits} />}
|
||||
{addOrphanVisitsRoute && <Route path="/server/:serverId/orphan-visits" component={OrphanVisits} />}
|
||||
<Route exact path="/server/:serverId/manage-tags" component={TagsList} />
|
||||
|
|
|
@ -37,6 +37,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
|
|||
'OrphanVisits',
|
||||
'ServerError',
|
||||
'Overview',
|
||||
'EditShortUrl',
|
||||
);
|
||||
bottle.decorator('MenuLayout', connect([ 'selectedServer', 'shortUrlsListParams' ], [ 'selectServer' ]));
|
||||
bottle.decorator('MenuLayout', withRouter);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { pipe, replace, trim } from 'ramda';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { SelectedServer } from '../servers/data';
|
||||
import { Settings, ShortUrlCreationSettings } from '../settings/reducers/settings';
|
||||
|
@ -19,8 +18,6 @@ interface CreateShortUrlConnectProps extends CreateShortUrlProps {
|
|||
resetCreateShortUrl: () => void;
|
||||
}
|
||||
|
||||
export const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
||||
|
||||
const getInitialState = (settings?: ShortUrlCreationSettings): ShortUrlData => ({
|
||||
longUrl: '',
|
||||
tags: [],
|
||||
|
|
76
src/short-urls/EditShortUrl.tsx
Normal file
76
src/short-urls/EditShortUrl.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { FC, useEffect } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { SelectedServer } from '../servers/data';
|
||||
import { Settings, ShortUrlCreationSettings } from '../settings/reducers/settings';
|
||||
import { OptionalString } from '../utils/utils';
|
||||
import { parseQuery } from '../utils/helpers/query';
|
||||
import Message from '../utils/Message';
|
||||
import { Result } from '../utils/Result';
|
||||
import { ShlinkApiError } from '../api/ShlinkApiError';
|
||||
import { ShortUrlFormProps } from './ShortUrlForm';
|
||||
import { ShortUrlDetail } from './reducers/shortUrlDetail';
|
||||
import { ShortUrl, ShortUrlData } from './data';
|
||||
|
||||
interface EditShortUrlConnectProps extends RouteComponentProps<{ shortCode: string }> {
|
||||
settings: Settings;
|
||||
selectedServer: SelectedServer;
|
||||
shortUrlDetail: ShortUrlDetail;
|
||||
getShortUrlDetail: (shortCode: string, domain: OptionalString) => void;
|
||||
}
|
||||
|
||||
const getInitialState = (shortUrl?: ShortUrl, settings?: ShortUrlCreationSettings): ShortUrlData => {
|
||||
const validateUrl = settings?.validateUrls ?? false;
|
||||
|
||||
if (!shortUrl) {
|
||||
return { longUrl: '', validateUrl };
|
||||
}
|
||||
|
||||
return {
|
||||
longUrl: shortUrl.longUrl,
|
||||
tags: shortUrl.tags,
|
||||
title: shortUrl.title ?? undefined,
|
||||
domain: shortUrl.domain ?? undefined,
|
||||
validSince: shortUrl.meta.validSince ?? undefined,
|
||||
validUntil: shortUrl.meta.validUntil ?? undefined,
|
||||
maxVisits: shortUrl.meta.maxVisits ?? undefined,
|
||||
validateUrl,
|
||||
};
|
||||
};
|
||||
|
||||
export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
|
||||
match: { params },
|
||||
location: { search },
|
||||
settings: { shortUrlCreation: shortUrlCreationSettings },
|
||||
selectedServer,
|
||||
shortUrlDetail,
|
||||
getShortUrlDetail,
|
||||
}: EditShortUrlConnectProps) => {
|
||||
const { loading, error, errorData, shortUrl } = shortUrlDetail;
|
||||
const { domain } = parseQuery<{ domain?: string }>(search);
|
||||
|
||||
useEffect(() => {
|
||||
getShortUrlDetail(params.shortCode, domain);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Message loading />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Result type="error">
|
||||
<ShlinkApiError errorData={errorData} fallbackMessage="An error occurred while loading short URL detail :(" />
|
||||
</Result>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ShortUrlForm
|
||||
initialState={getInitialState(shortUrl, shortUrlCreationSettings)}
|
||||
saving={false}
|
||||
selectedServer={selectedServer}
|
||||
mode="edit"
|
||||
onSave={async (shortUrlData) => Promise.resolve(console.log(shortUrlData))}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import { FC, useState } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { InputType } from 'reactstrap/lib/Input';
|
||||
import { Button, FormGroup, Input, Row } from 'reactstrap';
|
||||
import { isEmpty } from 'ramda';
|
||||
import { isEmpty, pipe, replace, trim } from 'ramda';
|
||||
import * as m from 'moment';
|
||||
import DateInput, { DateInputProps } from '../utils/DateInput';
|
||||
import {
|
||||
|
@ -18,7 +18,6 @@ import { Versions } from '../utils/helpers/version';
|
|||
import { DomainSelectorProps } from '../domains/DomainSelector';
|
||||
import { formatIsoDate } from '../utils/helpers/date';
|
||||
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
|
||||
import { normalizeTag } from './CreateShortUrl';
|
||||
import { ShortUrlData } from './data';
|
||||
import './ShortUrlForm.scss';
|
||||
|
||||
|
@ -34,19 +33,26 @@ export interface ShortUrlFormProps {
|
|||
selectedServer: SelectedServer;
|
||||
}
|
||||
|
||||
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
||||
|
||||
export const ShortUrlForm = (
|
||||
TagsSelector: FC<TagsSelectorProps>,
|
||||
ForServerVersion: FC<Versions>,
|
||||
DomainSelector: FC<DomainSelectorProps>,
|
||||
): FC<ShortUrlFormProps> => ({ mode, saving, onSave, initialState, selectedServer, children }) => { // eslint-disable-line complexity
|
||||
const [ shortUrlData, setShortUrlData ] = useState(initialState);
|
||||
const isEdit = mode === 'edit';
|
||||
const changeTags = (tags: string[]) => setShortUrlData({ ...shortUrlData, tags: tags.map(normalizeTag) });
|
||||
const reset = () => setShortUrlData(initialState);
|
||||
const submit = handleEventPreventingDefault(async () => onSave({
|
||||
...shortUrlData,
|
||||
validSince: formatIsoDate(shortUrlData.validSince) ?? undefined,
|
||||
validUntil: formatIsoDate(shortUrlData.validUntil) ?? undefined,
|
||||
}).then(reset).catch(() => {}));
|
||||
}).then(() => !isEdit && reset()).catch(() => {}));
|
||||
|
||||
useEffect(() => {
|
||||
setShortUrlData(initialState);
|
||||
}, [ initialState ]);
|
||||
|
||||
const renderOptionalInput = (id: NonDateFields, placeholder: string, type: InputType = 'text', props = {}) => (
|
||||
<FormGroup>
|
||||
|
@ -54,7 +60,7 @@ export const ShortUrlForm = (
|
|||
id={id}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={shortUrlData[id]}
|
||||
value={shortUrlData[id] ?? ''}
|
||||
onChange={(e) => setShortUrlData({ ...shortUrlData, [id]: e.target.value })}
|
||||
{...props}
|
||||
/>
|
||||
|
@ -93,7 +99,6 @@ export const ShortUrlForm = (
|
|||
const showDomainSelector = supportsListingDomains(selectedServer);
|
||||
const disableShortCodeLength = !supportsSettingShortCodeLength(selectedServer);
|
||||
const supportsTitle = supportsShortUrlTitle(selectedServer);
|
||||
const isEdit = mode === 'edit';
|
||||
|
||||
return (
|
||||
<form className="short-url-form" onSubmit={submit}>
|
||||
|
@ -191,7 +196,7 @@ export const ShortUrlForm = (
|
|||
disabled={saving || isEmpty(shortUrlData.longUrl)}
|
||||
className="btn-xs-block"
|
||||
>
|
||||
{saving ? 'Creating...' : 'Create'}
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
30
src/short-urls/helpers/ShortUrlDetailLink.tsx
Normal file
30
src/short-urls/helpers/ShortUrlDetailLink.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data';
|
||||
import { ShortUrl } from '../data';
|
||||
|
||||
export type LinkSuffix = 'visits' | 'edit';
|
||||
|
||||
export interface ShortUrlDetailLinkProps {
|
||||
shortUrl?: ShortUrl | null;
|
||||
selectedServer?: SelectedServer;
|
||||
suffix: LinkSuffix;
|
||||
}
|
||||
|
||||
const buildUrl = ({ id }: ServerWithId, { shortCode, domain }: ShortUrl, suffix: LinkSuffix) => {
|
||||
const query = domain ? `?domain=${domain}` : '';
|
||||
|
||||
return `/server/${id}/short-code/${shortCode}/${suffix}${query}`;
|
||||
};
|
||||
|
||||
const ShortUrlDetailLink: FC<ShortUrlDetailLinkProps & Record<string | number, any>> = (
|
||||
{ selectedServer, shortUrl, suffix, children, ...rest },
|
||||
) => {
|
||||
if (!selectedServer || !isServerWithId(selectedServer) || !shortUrl) {
|
||||
return <span {...rest}>{children}</span>;
|
||||
}
|
||||
|
||||
return <Link to={buildUrl(selectedServer, shortUrl, suffix)} {...rest}>{children}</Link>;
|
||||
};
|
||||
|
||||
export default ShortUrlDetailLink;
|
|
@ -4,10 +4,14 @@ import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
|||
import { UncontrolledTooltip } from 'reactstrap';
|
||||
import classNames from 'classnames';
|
||||
import { prettify } from '../../utils/helpers/numbers';
|
||||
import VisitStatsLink, { VisitStatsLinkProps } from './VisitStatsLink';
|
||||
import { ShortUrl } from '../data';
|
||||
import { SelectedServer } from '../../servers/data';
|
||||
import ShortUrlDetailLink from './ShortUrlDetailLink';
|
||||
import './ShortUrlVisitsCount.scss';
|
||||
|
||||
interface ShortUrlVisitsCountProps extends VisitStatsLinkProps {
|
||||
interface ShortUrlVisitsCountProps {
|
||||
shortUrl?: ShortUrl | null;
|
||||
selectedServer?: SelectedServer;
|
||||
visitsCount: number;
|
||||
active?: boolean;
|
||||
}
|
||||
|
@ -15,13 +19,13 @@ interface ShortUrlVisitsCountProps extends VisitStatsLinkProps {
|
|||
const ShortUrlVisitsCount = ({ visitsCount, shortUrl, selectedServer, active = false }: ShortUrlVisitsCountProps) => {
|
||||
const maxVisits = shortUrl?.meta?.maxVisits;
|
||||
const visitsLink = (
|
||||
<VisitStatsLink selectedServer={selectedServer} shortUrl={shortUrl}>
|
||||
<ShortUrlDetailLink selectedServer={selectedServer} shortUrl={shortUrl} suffix="visits">
|
||||
<strong
|
||||
className={classNames('short-url-visits-count__amount', { 'short-url-visits-count__amount--big': active })}
|
||||
>
|
||||
{prettify(visitsCount)}
|
||||
</strong>
|
||||
</VisitStatsLink>
|
||||
</ShortUrlDetailLink>
|
||||
);
|
||||
|
||||
if (!maxVisits) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import { useToggle } from '../../utils/helpers/hooks';
|
|||
import { ShortUrl, ShortUrlModalProps } from '../data';
|
||||
import { Versions } from '../../utils/helpers/version';
|
||||
import { SelectedServer } from '../../servers/data';
|
||||
import VisitStatsLink from './VisitStatsLink';
|
||||
import ShortUrlDetailLink from './ShortUrlDetailLink';
|
||||
import './ShortUrlsRowMenu.scss';
|
||||
|
||||
export interface ShortUrlsRowMenuProps {
|
||||
|
@ -44,10 +44,14 @@ const ShortUrlsRowMenu = (
|
|||
<FontAwesomeIcon icon={menuIcon} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right>
|
||||
<DropdownItem tag={VisitStatsLink} selectedServer={selectedServer} shortUrl={shortUrl}>
|
||||
<DropdownItem tag={ShortUrlDetailLink} selectedServer={selectedServer} shortUrl={shortUrl} suffix="visits">
|
||||
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
|
||||
</DropdownItem>
|
||||
|
||||
<DropdownItem tag={ShortUrlDetailLink} selectedServer={selectedServer} shortUrl={shortUrl} suffix="edit">
|
||||
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit short URL
|
||||
</DropdownItem>
|
||||
|
||||
<DropdownItem onClick={toggleTags}>
|
||||
<FontAwesomeIcon icon={tagsIcon} fixedWidth /> Edit tags
|
||||
</DropdownItem>
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data';
|
||||
import { ShortUrl } from '../data';
|
||||
|
||||
export interface VisitStatsLinkProps {
|
||||
shortUrl?: ShortUrl | null;
|
||||
selectedServer?: SelectedServer;
|
||||
}
|
||||
|
||||
const buildVisitsUrl = ({ id }: ServerWithId, { shortCode, domain }: ShortUrl) => {
|
||||
const query = domain ? `?domain=${domain}` : '';
|
||||
|
||||
return `/server/${id}/short-code/${shortCode}/visits${query}`;
|
||||
};
|
||||
|
||||
const VisitStatsLink: FC<VisitStatsLinkProps & Record<string | number, any>> = (
|
||||
{ selectedServer, shortUrl, children, ...rest },
|
||||
) => {
|
||||
if (!selectedServer || !isServerWithId(selectedServer) || !shortUrl) {
|
||||
return <span {...rest}>{children}</span>;
|
||||
}
|
||||
|
||||
return <Link to={buildVisitsUrl(selectedServer, shortUrl)} {...rest}>{children}</Link>;
|
||||
};
|
||||
|
||||
export default VisitStatsLink;
|
|
@ -5,6 +5,8 @@ import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilde
|
|||
import { OptionalString } from '../../utils/utils';
|
||||
import { GetState } from '../../container/types';
|
||||
import { shortUrlMatches } from '../helpers';
|
||||
import { ProblemDetailsError } from '../../api/types';
|
||||
import { parseApiError } from '../../api/utils';
|
||||
|
||||
/* eslint-disable padding-line-between-statements */
|
||||
export const GET_SHORT_URL_DETAIL_START = 'shlink/shortUrlDetail/GET_SHORT_URL_DETAIL_START';
|
||||
|
@ -16,20 +18,25 @@ export interface ShortUrlDetail {
|
|||
shortUrl?: ShortUrl;
|
||||
loading: boolean;
|
||||
error: boolean;
|
||||
errorData?: ProblemDetailsError;
|
||||
}
|
||||
|
||||
export interface ShortUrlDetailAction extends Action<string> {
|
||||
shortUrl: ShortUrl;
|
||||
}
|
||||
|
||||
export interface ShortUrlDetailFailedAction extends Action<string> {
|
||||
errorData?: ProblemDetailsError;
|
||||
}
|
||||
|
||||
const initialState: ShortUrlDetail = {
|
||||
loading: false,
|
||||
error: false,
|
||||
};
|
||||
|
||||
export default buildReducer<ShortUrlDetail, ShortUrlDetailAction>({
|
||||
export default buildReducer<ShortUrlDetail, ShortUrlDetailAction & ShortUrlDetailFailedAction>({
|
||||
[GET_SHORT_URL_DETAIL_START]: () => ({ loading: true, error: false }),
|
||||
[GET_SHORT_URL_DETAIL_ERROR]: () => ({ loading: false, error: true }),
|
||||
[GET_SHORT_URL_DETAIL_ERROR]: (_, { errorData }) => ({ loading: false, error: true, errorData }),
|
||||
[GET_SHORT_URL_DETAIL]: (_, { shortUrl }) => ({ shortUrl, ...initialState }),
|
||||
}, initialState);
|
||||
|
||||
|
@ -47,6 +54,6 @@ export const getShortUrlDetail = (buildShlinkApiClient: ShlinkApiClientBuilder)
|
|||
|
||||
dispatch<ShortUrlDetailAction>({ shortUrl, type: GET_SHORT_URL_DETAIL });
|
||||
} catch (e) {
|
||||
dispatch({ type: GET_SHORT_URL_DETAIL_ERROR });
|
||||
dispatch<ShortUrlDetailFailedAction>({ type: GET_SHORT_URL_DETAIL_ERROR, errorData: parseApiError(e) });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,6 +21,8 @@ import { ConnectDecorator } from '../../container/types';
|
|||
import { ShortUrlsTable } from '../ShortUrlsTable';
|
||||
import QrCodeModal from '../helpers/QrCodeModal';
|
||||
import { ShortUrlForm } from '../ShortUrlForm';
|
||||
import { EditShortUrl } from '../EditShortUrl';
|
||||
import { getShortUrlDetail } from '../reducers/shortUrlDetail';
|
||||
|
||||
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
// Components
|
||||
|
@ -54,6 +56,12 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
connect([ 'shortUrlCreationResult', 'selectedServer', 'settings' ], [ 'createShortUrl', 'resetCreateShortUrl' ]),
|
||||
);
|
||||
|
||||
bottle.serviceFactory('EditShortUrl', EditShortUrl, 'ShortUrlForm');
|
||||
bottle.decorator(
|
||||
'EditShortUrl',
|
||||
connect([ 'shortUrlDetail', 'selectedServer', 'settings' ], [ 'getShortUrlDetail' ]),
|
||||
);
|
||||
|
||||
bottle.serviceFactory('DeleteShortUrlModal', () => DeleteShortUrlModal);
|
||||
bottle.decorator('DeleteShortUrlModal', connect([ 'shortUrlDeletion' ], [ 'deleteShortUrl', 'resetDeleteShortUrl' ]));
|
||||
|
||||
|
@ -89,6 +97,8 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
bottle.serviceFactory('editShortUrlMeta', editShortUrlMeta, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('resetShortUrlMeta', () => resetShortUrlMeta);
|
||||
|
||||
bottle.serviceFactory('getShortUrlDetail', getShortUrlDetail, 'buildShlinkApiClient');
|
||||
|
||||
bottle.serviceFactory('editShortUrl', editShortUrl, 'buildShlinkApiClient');
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import Bottle from 'bottlejs';
|
||||
import ShortUrlVisits from '../ShortUrlVisits';
|
||||
import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits';
|
||||
import { getShortUrlDetail } from '../../short-urls/reducers/shortUrlDetail';
|
||||
import MapModal from '../helpers/MapModal';
|
||||
import { createNewVisits } from '../reducers/visitCreation';
|
||||
import TagVisits from '../TagVisits';
|
||||
|
@ -41,7 +40,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
|
||||
// Actions
|
||||
bottle.serviceFactory('getShortUrlVisits', getShortUrlVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('getShortUrlDetail', getShortUrlDetail, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('cancelGetShortUrlVisits', () => cancelGetShortUrlVisits);
|
||||
|
||||
bottle.serviceFactory('getTagVisits', getTagVisits, 'buildShlinkApiClient');
|
||||
|
|
|
@ -11,7 +11,7 @@ import { SemVer } from '../../src/utils/helpers/version';
|
|||
describe('<MenuLayout />', () => {
|
||||
const ServerError = jest.fn();
|
||||
const C = jest.fn();
|
||||
const MenuLayout = createMenuLayout(C, C, C, C, C, C, C, ServerError, C);
|
||||
const MenuLayout = createMenuLayout(C, C, C, C, C, C, C, ServerError, C, C);
|
||||
let wrapper: ShallowWrapper;
|
||||
const createWrapper = (selectedServer: SelectedServer) => {
|
||||
wrapper = shallow(
|
||||
|
@ -49,11 +49,11 @@ describe('<MenuLayout />', () => {
|
|||
});
|
||||
|
||||
it.each([
|
||||
[ '2.1.0' as SemVer, 6 ],
|
||||
[ '2.2.0' as SemVer, 7 ],
|
||||
[ '2.5.0' as SemVer, 7 ],
|
||||
[ '2.6.0' as SemVer, 8 ],
|
||||
[ '2.7.0' as SemVer, 8 ],
|
||||
[ '2.1.0' as SemVer, 7 ],
|
||||
[ '2.2.0' as SemVer, 8 ],
|
||||
[ '2.5.0' as SemVer, 8 ],
|
||||
[ '2.6.0' as SemVer, 9 ],
|
||||
[ '2.7.0' as SemVer, 9 ],
|
||||
])('has expected amount of routes based on selected server\'s version', (version, expectedAmountOfRoutes) => {
|
||||
const selectedServer = Mock.of<ReachableServer>({ version });
|
||||
const wrapper = createWrapper(selectedServer).dive();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import VisitStatsLink from '../../../src/short-urls/helpers/VisitStatsLink';
|
||||
import ShortUrlDetailLink, { LinkSuffix } from '../../../src/short-urls/helpers/ShortUrlDetailLink';
|
||||
import { NotFoundServer, ReachableServer } from '../../../src/servers/data';
|
||||
import { ShortUrl } from '../../../src/short-urls/data';
|
||||
|
||||
describe('<VisitStatsLink />', () => {
|
||||
describe('<ShortUrlDetailLink />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
afterEach(() => wrapper?.unmount());
|
||||
|
@ -19,7 +19,11 @@ describe('<VisitStatsLink />', () => {
|
|||
[ null, Mock.all<ShortUrl>() ],
|
||||
[ undefined, Mock.all<ShortUrl>() ],
|
||||
])('only renders a plain span when either server or short URL are not set', (selectedServer, shortUrl) => {
|
||||
wrapper = shallow(<VisitStatsLink selectedServer={selectedServer} shortUrl={shortUrl}>Something</VisitStatsLink>);
|
||||
wrapper = shallow(
|
||||
<ShortUrlDetailLink selectedServer={selectedServer} shortUrl={shortUrl} suffix="visits">
|
||||
Something
|
||||
</ShortUrlDetailLink>,
|
||||
);
|
||||
const link = wrapper.find(Link);
|
||||
|
||||
expect(link).toHaveLength(0);
|
||||
|
@ -30,15 +34,33 @@ describe('<VisitStatsLink />', () => {
|
|||
[
|
||||
Mock.of<ReachableServer>({ id: '1' }),
|
||||
Mock.of<ShortUrl>({ shortCode: 'abc123' }),
|
||||
'visits' as LinkSuffix,
|
||||
'/server/1/short-code/abc123/visits',
|
||||
],
|
||||
[
|
||||
Mock.of<ReachableServer>({ id: '3' }),
|
||||
Mock.of<ShortUrl>({ shortCode: 'def456', domain: 'example.com' }),
|
||||
'visits' as LinkSuffix,
|
||||
'/server/3/short-code/def456/visits?domain=example.com',
|
||||
],
|
||||
])('renders link with expected query when', (selectedServer, shortUrl, expectedLink) => {
|
||||
wrapper = shallow(<VisitStatsLink selectedServer={selectedServer} shortUrl={shortUrl}>Something</VisitStatsLink>);
|
||||
[
|
||||
Mock.of<ReachableServer>({ id: '1' }),
|
||||
Mock.of<ShortUrl>({ shortCode: 'abc123' }),
|
||||
'edit' as LinkSuffix,
|
||||
'/server/1/short-code/abc123/edit',
|
||||
],
|
||||
[
|
||||
Mock.of<ReachableServer>({ id: '3' }),
|
||||
Mock.of<ShortUrl>({ shortCode: 'def456', domain: 'example.com' }),
|
||||
'edit' as LinkSuffix,
|
||||
'/server/3/short-code/def456/edit?domain=example.com',
|
||||
],
|
||||
])('renders link with expected query when', (selectedServer, shortUrl, suffix, expectedLink) => {
|
||||
wrapper = shallow(
|
||||
<ShortUrlDetailLink selectedServer={selectedServer} shortUrl={shortUrl} suffix={suffix}>
|
||||
Something
|
||||
</ShortUrlDetailLink>,
|
||||
);
|
||||
const link = wrapper.find(Link);
|
||||
const to = link.prop('to');
|
||||
|
|
@ -51,7 +51,7 @@ describe('<ShortUrlsRowMenu />', () => {
|
|||
const wrapper = createWrapper();
|
||||
const items = wrapper.find(DropdownItem);
|
||||
|
||||
expect(items).toHaveLength(7);
|
||||
expect(items).toHaveLength(8);
|
||||
expect(items.find('[divider]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ describe('shortUrlDetailReducer', () => {
|
|||
const buildGetState = (shortUrlsList?: ShortUrlsList) => () => Mock.of<ShlinkState>({ shortUrlsList });
|
||||
|
||||
it('dispatches start and error when promise is rejected', async () => {
|
||||
const ShlinkApiClient = buildApiClientMock(Promise.reject());
|
||||
const ShlinkApiClient = buildApiClientMock(Promise.reject({}));
|
||||
|
||||
await getShortUrlDetail(() => ShlinkApiClient)('abc123', '')(dispatchMock, buildGetState());
|
||||
|
||||
|
|
Loading…
Reference in a new issue