Merge pull request #494 from acelaya-forks/feature/remove-old-shlink

Feature/remove old shlink
This commit is contained in:
Alejandro Celaya 2021-09-25 11:54:26 +02:00 committed by GitHub
commit 15b7fd5c93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 78 additions and 156 deletions

View file

@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
## [Unreleased] ## [3.3.0] - 2021-09-25
### Added ### Added
* [#465](https://github.com/shlinkio/shlink-web-client/issues/465) Added new page to manage domains and their redirects, when consuming Shlink 2.8 or higher. * [#465](https://github.com/shlinkio/shlink-web-client/issues/465) Added new page to manage domains and their redirects, when consuming Shlink 2.8 or higher.
* [#460](https://github.com/shlinkio/shlink-web-client/issues/460) Added dynamic title on hover for tags with a very long title. * [#460](https://github.com/shlinkio/shlink-web-client/issues/460) Added dynamic title on hover for tags with a very long title.
@ -31,7 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* *Nothing* * *Nothing*
### Removed ### Removed
* *Nothing* * [#491](https://github.com/shlinkio/shlink-web-client/issues/491) Dropped support for Shlink older than v2.4.0.
### Fixed ### Fixed
* *Nothing* * *Nothing*

View file

@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames'; import classNames from 'classnames';
import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { withSelectedServer } from '../servers/helpers/withSelectedServer';
import { useSwipeable, useToggle } from '../utils/helpers/hooks'; import { useSwipeable, useToggle } from '../utils/helpers/hooks';
import { supportsDomainRedirects, supportsOrphanVisits, supportsTagVisits } from '../utils/helpers/features'; import { supportsDomainRedirects, supportsOrphanVisits } from '../utils/helpers/features';
import { isReachableServer } from '../servers/data'; import { isReachableServer } from '../servers/data';
import NotFound from './NotFound'; import NotFound from './NotFound';
import { AsideMenuProps } from './AsideMenu'; import { AsideMenuProps } from './AsideMenu';
@ -32,7 +32,6 @@ const MenuLayout = (
return <ServerError />; return <ServerError />;
} }
const addTagsVisitsRoute = supportsTagVisits(selectedServer);
const addOrphanVisitsRoute = supportsOrphanVisits(selectedServer); const addOrphanVisitsRoute = supportsOrphanVisits(selectedServer);
const addManageDomainsRoute = supportsDomainRedirects(selectedServer); const addManageDomainsRoute = supportsDomainRedirects(selectedServer);
const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible }); const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible });
@ -54,7 +53,7 @@ const MenuLayout = (
<Route exact path="/server/:serverId/create-short-url" component={CreateShortUrl} /> <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/visits" component={ShortUrlVisits} />
<Route path="/server/:serverId/short-code/:shortCode/edit" component={EditShortUrl} /> <Route path="/server/:serverId/short-code/:shortCode/edit" component={EditShortUrl} />
{addTagsVisitsRoute && <Route path="/server/:serverId/tag/:tag/visits" component={TagVisits} />} <Route path="/server/:serverId/tag/:tag/visits" component={TagVisits} />
{addOrphanVisitsRoute && <Route path="/server/:serverId/orphan-visits" component={OrphanVisits} />} {addOrphanVisitsRoute && <Route path="/server/:serverId/orphan-visits" component={OrphanVisits} />}
<Route exact path="/server/:serverId/manage-tags" component={TagsList} /> <Route exact path="/server/:serverId/manage-tags" component={TagsList} />
{addManageDomainsRoute && <Route exact path="/server/:serverId/manage-domains" component={ManageDomains} />} {addManageDomainsRoute && <Route exact path="/server/:serverId/manage-domains" component={ManageDomains} />}

View file

@ -55,14 +55,7 @@ export const Overview = (
<div className="col-md-6 col-xl-3"> <div className="col-md-6 col-xl-3">
<Card className="overview__card mb-3" body> <Card className="overview__card mb-3" body>
<CardTitle tag="h5" className="overview__card-title">Visits</CardTitle> <CardTitle tag="h5" className="overview__card-title">Visits</CardTitle>
<CardText tag="h2"> <CardText tag="h2">{loadingVisits ? 'Loading...' : prettify(visitsCount)}</CardText>
<ForServerVersion minVersion="2.2.0">
{loadingVisits ? 'Loading...' : prettify(visitsCount)}
</ForServerVersion>
<ForServerVersion maxVersion="2.1.*">
<small className="text-muted"><i>Shlink 2.2 is needed</i></small>
</ForServerVersion>
</CardText>
</Card> </Card>
</div> </div>
<div className="col-md-6 col-xl-3"> <div className="col-md-6 col-xl-3">

View file

@ -5,13 +5,7 @@ import { isEmpty, pipe, replace, trim } from 'ramda';
import classNames from 'classnames'; import classNames from 'classnames';
import { parseISO } from 'date-fns'; import { parseISO } from 'date-fns';
import DateInput, { DateInputProps } from '../utils/DateInput'; import DateInput, { DateInputProps } from '../utils/DateInput';
import { import { supportsCrawlableVisits, supportsShortUrlTitle } from '../utils/helpers/features';
supportsCrawlableVisits,
supportsListingDomains,
supportsSettingShortCodeLength,
supportsShortUrlTitle,
supportsValidateUrl,
} from '../utils/helpers/features';
import { SimpleCard } from '../utils/SimpleCard'; import { SimpleCard } from '../utils/SimpleCard';
import { handleEventPreventingDefault, hasValue } from '../utils/utils'; import { handleEventPreventingDefault, hasValue } from '../utils/utils';
import Checkbox from '../utils/Checkbox'; import Checkbox from '../utils/Checkbox';
@ -102,17 +96,13 @@ export const ShortUrlForm = (
</> </>
); );
const showDomainSelector = supportsListingDomains(selectedServer);
const disableShortCodeLength = !supportsSettingShortCodeLength(selectedServer);
const supportsTitle = supportsShortUrlTitle(selectedServer); const supportsTitle = supportsShortUrlTitle(selectedServer);
const showCustomizeCard = supportsTitle || !isEdit; const showCustomizeCard = supportsTitle || !isEdit;
const limitAccessCardClasses = classNames('mb-3', { const limitAccessCardClasses = classNames('mb-3', {
'col-sm-6': showCustomizeCard, 'col-sm-6': showCustomizeCard,
'col-sm-12': !showCustomizeCard, 'col-sm-12': !showCustomizeCard,
}); });
const showValidateUrl = supportsValidateUrl(selectedServer);
const showCrawlableControl = supportsCrawlableVisits(selectedServer); const showCrawlableControl = supportsCrawlableVisits(selectedServer);
const showExtraValidationsCard = showValidateUrl || showCrawlableControl || !isEdit;
return ( return (
<form className="short-url-form" onSubmit={submit}> <form className="short-url-form" onSubmit={submit}>
@ -139,22 +129,16 @@ export const ShortUrlForm = (
<div className="col-lg-6"> <div className="col-lg-6">
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', { {renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
min: 4, min: 4,
disabled: disableShortCodeLength || hasValue(shortUrlData.customSlug), disabled: hasValue(shortUrlData.customSlug),
...disableShortCodeLength && {
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
},
})} })}
</div> </div>
</Row> </Row>
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text')}
{showDomainSelector && (
<FormGroup> <FormGroup>
<DomainSelector <DomainSelector
value={shortUrlData.domain} value={shortUrlData.domain}
onChange={(domain?: string) => setShortUrlData({ ...shortUrlData, domain })} onChange={(domain?: string) => setShortUrlData({ ...shortUrlData, domain })}
/> />
</FormGroup> </FormGroup>
)}
</> </>
)} )}
</SimpleCard> </SimpleCard>
@ -170,9 +154,7 @@ export const ShortUrlForm = (
</div> </div>
</Row> </Row>
{showExtraValidationsCard && (
<SimpleCard title="Extra checks" className="mb-3"> <SimpleCard title="Extra checks" className="mb-3">
{showValidateUrl && (
<ShortUrlFormCheckboxGroup <ShortUrlFormCheckboxGroup
infoTooltip="If checked, Shlink will try to reach the long URL, failing in case it's not publicly accessible." infoTooltip="If checked, Shlink will try to reach the long URL, failing in case it's not publicly accessible."
checked={shortUrlData.validateUrl} checked={shortUrlData.validateUrl}
@ -180,7 +162,6 @@ export const ShortUrlForm = (
> >
Validate URL Validate URL
</ShortUrlFormCheckboxGroup> </ShortUrlFormCheckboxGroup>
)}
{showCrawlableControl && ( {showCrawlableControl && (
<ShortUrlFormCheckboxGroup <ShortUrlFormCheckboxGroup
infoTooltip="This short URL will be included in the robots.txt for your Shlink instance, allowing web crawlers (like Google) to index it." infoTooltip="This short URL will be included in the robots.txt for your Shlink instance, allowing web crawlers (like Google) to index it."
@ -204,7 +185,6 @@ export const ShortUrlForm = (
</p> </p>
)} )}
</SimpleCard> </SimpleCard>
)}
</> </>
)} )}

View file

@ -10,7 +10,6 @@ import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon';
import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes'; import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes';
import { import {
supportsQrCodeSizeInQuery, supportsQrCodeSizeInQuery,
supportsQrCodeSvgFormat,
supportsQrCodeMargin, supportsQrCodeMargin,
supportsQrErrorCorrection, supportsQrErrorCorrection,
} from '../../utils/helpers/features'; } from '../../utils/helpers/features';
@ -33,10 +32,10 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC<Vers
const [ errorCorrection, setErrorCorrection ] = useState<QrErrorCorrection>('L'); const [ errorCorrection, setErrorCorrection ] = useState<QrErrorCorrection>('L');
const capabilities: QrCodeCapabilities = useMemo(() => ({ const capabilities: QrCodeCapabilities = useMemo(() => ({
useSizeInPath: !supportsQrCodeSizeInQuery(selectedServer), useSizeInPath: !supportsQrCodeSizeInQuery(selectedServer),
svgIsSupported: supportsQrCodeSvgFormat(selectedServer),
marginIsSupported: supportsQrCodeMargin(selectedServer), marginIsSupported: supportsQrCodeMargin(selectedServer),
errorCorrectionIsSupported: supportsQrErrorCorrection(selectedServer), errorCorrectionIsSupported: supportsQrErrorCorrection(selectedServer),
}), [ selectedServer ]); }), [ selectedServer ]);
const willRenderThreeControls = capabilities.marginIsSupported !== capabilities.errorCorrectionIsSupported;
const qrCodeUrl = useMemo( const qrCodeUrl = useMemo(
() => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }, capabilities), () => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }, capabilities),
[ shortUrl, size, format, margin, errorCorrection, capabilities ], [ shortUrl, size, format, margin, errorCorrection, capabilities ],
@ -58,11 +57,7 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC<Vers
<ModalBody> <ModalBody>
<Row> <Row>
<FormGroup <FormGroup
className={classNames({ className={classNames({ 'col-md-4': willRenderThreeControls, 'col-md-6': !willRenderThreeControls })}
'col-md-4': capabilities.marginIsSupported && capabilities.svgIsSupported && !capabilities.errorCorrectionIsSupported,
'col-md-6': capabilities.errorCorrectionIsSupported || (!capabilities.marginIsSupported && capabilities.svgIsSupported) || (capabilities.marginIsSupported && !capabilities.svgIsSupported),
'col-12': !capabilities.marginIsSupported && !capabilities.svgIsSupported && !capabilities.errorCorrectionIsSupported,
})}
> >
<label className="mb-0">Size: {size}px</label> <label className="mb-0">Size: {size}px</label>
<input <input
@ -76,7 +71,7 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC<Vers
/> />
</FormGroup> </FormGroup>
{capabilities.marginIsSupported && ( {capabilities.marginIsSupported && (
<FormGroup className={capabilities.svgIsSupported && !capabilities.errorCorrectionIsSupported ? 'col-md-4' : 'col-md-6'}> <FormGroup className={willRenderThreeControls ? 'col-md-4' : 'col-md-6'}>
<label className="mb-0">Margin: {margin}px</label> <label className="mb-0">Margin: {margin}px</label>
<input <input
type="range" type="range"
@ -89,11 +84,9 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC<Vers
/> />
</FormGroup> </FormGroup>
)} )}
{capabilities.svgIsSupported && ( <FormGroup className={willRenderThreeControls ? 'col-md-4' : 'col-md-6'}>
<FormGroup className={capabilities.marginIsSupported && !capabilities.errorCorrectionIsSupported ? 'col-md-4' : 'col-md-6'}>
<QrFormatDropdown format={format} setFormat={setFormat} /> <QrFormatDropdown format={format} setFormat={setFormat} />
</FormGroup> </FormGroup>
)}
{capabilities.errorCorrectionIsSupported && ( {capabilities.errorCorrectionIsSupported && (
<FormGroup className="col-md-6"> <FormGroup className="col-md-6">
<QrErrorCorrectionDropdown errorCorrection={errorCorrection} setErrorCorrection={setErrorCorrection} /> <QrErrorCorrectionDropdown errorCorrection={errorCorrection} setErrorCorrection={setErrorCorrection} />

View file

@ -5,7 +5,6 @@ import { FC, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { prettify } from '../utils/helpers/numbers'; import { prettify } from '../utils/helpers/numbers';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { Versions } from '../utils/helpers/version';
import ColorGenerator from '../utils/services/ColorGenerator'; import ColorGenerator from '../utils/services/ColorGenerator';
import { isServerWithId, SelectedServer } from '../servers/data'; import { isServerWithId, SelectedServer } from '../servers/data';
import TagBullet from './helpers/TagBullet'; import TagBullet from './helpers/TagBullet';
@ -25,16 +24,13 @@ const isTruncated = (el: HTMLElement | undefined): boolean => !!el && el.scrollW
const TagCard = ( const TagCard = (
DeleteTagConfirmModal: FC<TagModalProps>, DeleteTagConfirmModal: FC<TagModalProps>,
EditTagModal: FC<TagModalProps>, EditTagModal: FC<TagModalProps>,
ForServerVersion: FC<Versions>,
colorGenerator: ColorGenerator, colorGenerator: ColorGenerator,
) => ({ tag, tagStats, selectedServer, displayed, toggle }: TagCardProps) => { ) => ({ tag, tagStats, selectedServer, displayed, toggle }: TagCardProps) => {
const [ isDeleteModalOpen, toggleDelete ] = useToggle(); const [ isDeleteModalOpen, toggleDelete ] = useToggle();
const [ isEditModalOpen, toggleEdit ] = useToggle(); const [ isEditModalOpen, toggleEdit ] = useToggle();
const [ hasTitle,, displayTitle ] = useToggle(); const [ hasTitle,, displayTitle ] = useToggle();
const titleRef = useRef<HTMLElement>(); const titleRef = useRef<HTMLElement>();
const serverId = isServerWithId(selectedServer) ? selectedServer.id : ''; const serverId = isServerWithId(selectedServer) ? selectedServer.id : '';
const shortUrlsLink = `/server/${serverId}/list-short-urls/1?tag=${encodeURIComponent(tag)}`;
useEffect(() => { useEffect(() => {
if (isTruncated(titleRef.current)) { if (isTruncated(titleRef.current)) {
@ -59,12 +55,7 @@ const TagCard = (
}} }}
> >
<TagBullet tag={tag} colorGenerator={colorGenerator} /> <TagBullet tag={tag} colorGenerator={colorGenerator} />
<ForServerVersion minVersion="2.2.0">
<span className="tag-card__tag-name" onClick={toggle}>{tag}</span> <span className="tag-card__tag-name" onClick={toggle}>{tag}</span>
</ForServerVersion>
<ForServerVersion maxVersion="2.1.*">
<Link to={shortUrlsLink}>{tag}</Link>
</ForServerVersion>
</h5> </h5>
</CardHeader> </CardHeader>
@ -72,7 +63,7 @@ const TagCard = (
<Collapse isOpen={displayed}> <Collapse isOpen={displayed}>
<CardBody className="tag-card__body"> <CardBody className="tag-card__body">
<Link <Link
to={shortUrlsLink} to={`/server/${serverId}/list-short-urls/1?tag=${encodeURIComponent(tag)}`}
className="btn btn-outline-secondary btn-block d-flex justify-content-between align-items-center mb-1" className="btn btn-outline-secondary btn-block d-flex justify-content-between align-items-center mb-1"
> >
<span className="text-ellipsis"><FontAwesomeIcon icon={faLink} className="mr-2" />Short URLs</span> <span className="text-ellipsis"><FontAwesomeIcon icon={faLink} className="mr-2" />Short URLs</span>

View file

@ -18,14 +18,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('TagsSelector', TagsSelector, 'ColorGenerator'); bottle.serviceFactory('TagsSelector', TagsSelector, 'ColorGenerator');
bottle.decorator('TagsSelector', connect([ 'tagsList', 'settings' ], [ 'listTags' ])); bottle.decorator('TagsSelector', connect([ 'tagsList', 'settings' ], [ 'listTags' ]));
bottle.serviceFactory( bottle.serviceFactory('TagCard', TagCard, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator');
'TagCard',
TagCard,
'DeleteTagConfirmModal',
'EditTagModal',
'ForServerVersion',
'ColorGenerator',
);
bottle.serviceFactory('DeleteTagConfirmModal', () => DeleteTagConfirmModal); bottle.serviceFactory('DeleteTagConfirmModal', () => DeleteTagConfirmModal);
bottle.decorator('DeleteTagConfirmModal', connect([ 'tagDelete' ], [ 'deleteTag', 'tagDeleted' ])); bottle.decorator('DeleteTagConfirmModal', connect([ 'tagDelete' ], [ 'deleteTag', 'tagDeleted' ]));

View file

@ -4,16 +4,6 @@ import { versionMatch, Versions } from './version';
const serverMatchesVersions = (versions: Versions) => (selectedServer: SelectedServer): boolean => const serverMatchesVersions = (versions: Versions) => (selectedServer: SelectedServer): boolean =>
isReachableServer(selectedServer) && versionMatch(selectedServer.version, versions); isReachableServer(selectedServer) && versionMatch(selectedServer.version, versions);
export const supportsSettingShortCodeLength = serverMatchesVersions({ minVersion: '2.1.0' });
export const supportsTagVisits = serverMatchesVersions({ minVersion: '2.2.0' });
export const supportsListingDomains = serverMatchesVersions({ minVersion: '2.4.0' });
export const supportsQrCodeSvgFormat = supportsListingDomains;
export const supportsValidateUrl = supportsListingDomains;
export const supportsQrCodeSizeInQuery = serverMatchesVersions({ minVersion: '2.5.0' }); export const supportsQrCodeSizeInQuery = serverMatchesVersions({ minVersion: '2.5.0' });
export const supportsShortUrlTitle = serverMatchesVersions({ minVersion: '2.6.0' }); export const supportsShortUrlTitle = serverMatchesVersions({ minVersion: '2.6.0' });

View file

@ -3,7 +3,6 @@ import { stringifyQuery } from './query';
export interface QrCodeCapabilities { export interface QrCodeCapabilities {
useSizeInPath: boolean; useSizeInPath: boolean;
svgIsSupported: boolean;
marginIsSupported: boolean; marginIsSupported: boolean;
errorCorrectionIsSupported: boolean; errorCorrectionIsSupported: boolean;
} }
@ -22,12 +21,12 @@ export interface QrCodeOptions {
export const buildQrCodeUrl = ( export const buildQrCodeUrl = (
shortUrl: string, shortUrl: string,
{ size, format, margin, errorCorrection }: QrCodeOptions, { size, format, margin, errorCorrection }: QrCodeOptions,
{ useSizeInPath, svgIsSupported, marginIsSupported, errorCorrectionIsSupported }: QrCodeCapabilities, { useSizeInPath, marginIsSupported, errorCorrectionIsSupported }: QrCodeCapabilities,
): string => { ): string => {
const baseUrl = `${shortUrl}/qr-code${useSizeInPath ? `/${size}` : ''}`; const baseUrl = `${shortUrl}/qr-code${useSizeInPath ? `/${size}` : ''}`;
const query = stringifyQuery({ const query = stringifyQuery({
size: useSizeInPath ? undefined : size, size: useSizeInPath ? undefined : size,
format: svgIsSupported ? format : undefined, format,
margin: marginIsSupported && margin > 0 ? margin : undefined, margin: marginIsSupported && margin > 0 ? margin : undefined,
errorCorrection: errorCorrectionIsSupported ? errorCorrection : undefined, errorCorrection: errorCorrectionIsSupported ? errorCorrection : undefined,
}); });

View file

@ -49,8 +49,6 @@ describe('<MenuLayout />', () => {
}); });
it.each([ it.each([
[ '2.1.0' as SemVer, 7 ],
[ '2.2.0' as SemVer, 8 ],
[ '2.5.0' as SemVer, 8 ], [ '2.5.0' as SemVer, 8 ],
[ '2.6.0' as SemVer, 9 ], [ '2.6.0' as SemVer, 9 ],
[ '2.7.0' as SemVer, 9 ], [ '2.7.0' as SemVer, 9 ],

View file

@ -64,13 +64,6 @@ describe('<Overview />', () => {
expect(cards.at(3).html()).toContain(prettify(3)); expect(cards.at(3).html()).toContain(prettify(3));
}); });
it('displays warning in first card for old shlink versions', () => {
const wrapper = createWrapper();
const firstCard = wrapper.find(CardText).first();
expect(firstCard.html()).toContain('Shlink 2.2 is needed');
});
it('nests complex components', () => { it('nests complex components', () => {
const wrapper = createWrapper(); const wrapper = createWrapper();

View file

@ -13,9 +13,10 @@ import { parseDate } from '../../src/utils/helpers/date';
describe('<ShortUrlForm />', () => { describe('<ShortUrlForm />', () => {
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
const TagsSelector = () => null; const TagsSelector = () => null;
const DomainSelector = () => null;
const createShortUrl = jest.fn(async () => Promise.resolve()); const createShortUrl = jest.fn(async () => Promise.resolve());
const createWrapper = (selectedServer: SelectedServer = null, mode: Mode = 'create') => { const createWrapper = (selectedServer: SelectedServer = null, mode: Mode = 'create') => {
const ShortUrlForm = createShortUrlForm(TagsSelector, () => null); const ShortUrlForm = createShortUrlForm(TagsSelector, DomainSelector);
wrapper = shallow( wrapper = shallow(
<ShortUrlForm <ShortUrlForm
@ -41,7 +42,7 @@ describe('<ShortUrlForm />', () => {
wrapper.find(Input).first().simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } }); wrapper.find(Input).first().simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } });
wrapper.find('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]); wrapper.find('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]);
wrapper.find('#customSlug').simulate('change', { target: { value: 'my-slug' } }); wrapper.find('#customSlug').simulate('change', { target: { value: 'my-slug' } });
wrapper.find('#domain').simulate('change', { target: { value: 'example.com' } }); wrapper.find(DomainSelector).simulate('change', 'example.com');
wrapper.find('#maxVisits').simulate('change', { target: { value: '20' } }); wrapper.find('#maxVisits').simulate('change', { target: { value: '20' } });
wrapper.find('#shortCodeLength').simulate('change', { target: { value: 15 } }); wrapper.find('#shortCodeLength').simulate('change', { target: { value: 15 } });
wrapper.find(DateInput).at(0).simulate('change', validSince); wrapper.find(DateInput).at(0).simulate('change', validSince);
@ -68,12 +69,8 @@ describe('<ShortUrlForm />', () => {
[ null, 'create-basic' as Mode, 0 ], [ null, 'create-basic' as Mode, 0 ],
[ Mock.of<ReachableServer>({ version: '2.6.0' }), 'create' as Mode, 4 ], [ Mock.of<ReachableServer>({ version: '2.6.0' }), 'create' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.5.0' }), 'create' as Mode, 4 ], [ Mock.of<ReachableServer>({ version: '2.5.0' }), 'create' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.4.0' }), 'create' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.3.0' }), 'create' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.6.0' }), 'edit' as Mode, 4 ], [ Mock.of<ReachableServer>({ version: '2.6.0' }), 'edit' as Mode, 4 ],
[ Mock.of<ReachableServer>({ version: '2.5.0' }), 'edit' as Mode, 3 ], [ Mock.of<ReachableServer>({ version: '2.5.0' }), 'edit' as Mode, 3 ],
[ Mock.of<ReachableServer>({ version: '2.4.0' }), 'edit' as Mode, 3 ],
[ Mock.of<ReachableServer>({ version: '2.3.0' }), 'edit' as Mode, 2 ],
])( ])(
'renders expected amount of cards based on server capabilities and mode', 'renders expected amount of cards based on server capabilities and mode',
(selectedServer, mode, expectedAmountOfCards) => { (selectedServer, mode, expectedAmountOfCards) => {

View file

@ -43,9 +43,6 @@ describe('<QrCodeModal />', () => {
}); });
it.each([ it.each([
[ '2.3.0' as SemVer, 0, '/qr-code/300' ],
[ '2.4.0' as SemVer, 0, '/qr-code/300?format=png' ],
[ '2.4.0' as SemVer, 10, '/qr-code/300?format=png' ],
[ '2.5.0' as SemVer, 0, '/qr-code?size=300&format=png' ], [ '2.5.0' as SemVer, 0, '/qr-code?size=300&format=png' ],
[ '2.6.0' as SemVer, 0, '/qr-code?size=300&format=png' ], [ '2.6.0' as SemVer, 0, '/qr-code?size=300&format=png' ],
[ '2.6.0' as SemVer, 10, '/qr-code?size=300&format=png&margin=10' ], [ '2.6.0' as SemVer, 10, '/qr-code?size=300&format=png&margin=10' ],
@ -90,8 +87,6 @@ describe('<QrCodeModal />', () => {
}); });
it.each([ it.each([
[ '2.3.0' as SemVer, 0, 'col-12' ],
[ '2.4.0' as SemVer, 1, 'col-md-6' ],
[ '2.6.0' as SemVer, 1, 'col-md-4' ], [ '2.6.0' as SemVer, 1, 'col-md-4' ],
[ '2.8.0' as SemVer, 2, 'col-md-6' ], [ '2.8.0' as SemVer, 2, 'col-md-6' ],
])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => { ])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => {

View file

@ -14,7 +14,7 @@ describe('<TagCard />', () => {
}; };
const DeleteTagConfirmModal = jest.fn(); const DeleteTagConfirmModal = jest.fn();
const EditTagModal = jest.fn(); const EditTagModal = jest.fn();
const TagCard = createTagCard(DeleteTagConfirmModal, EditTagModal, () => null, Mock.all<ColorGenerator>()); const TagCard = createTagCard(DeleteTagConfirmModal, EditTagModal, Mock.all<ColorGenerator>());
const createWrapper = (tag = 'ssr') => { const createWrapper = (tag = 'ssr') => {
wrapper = shallow( wrapper = shallow(
<TagCard <TagCard
@ -65,9 +65,10 @@ describe('<TagCard />', () => {
it('shows expected tag stats', () => { it('shows expected tag stats', () => {
const links = wrapper.find(Link); const links = wrapper.find(Link);
expect(links.at(1).prop('to')).toEqual('/server/1/list-short-urls/1?tag=ssr'); expect(links).toHaveLength(2);
expect(links.at(1).text()).toContain('48'); expect(links.at(0).prop('to')).toEqual('/server/1/list-short-urls/1?tag=ssr');
expect(links.at(2).prop('to')).toEqual('/server/1/tag/ssr/visits'); expect(links.at(0).text()).toContain('48');
expect(links.at(2).text()).toContain('23,257'); expect(links.at(1).prop('to')).toEqual('/server/1/tag/ssr/visits');
expect(links.at(1).text()).toContain('23,257');
}); });
}); });

View file

@ -6,67 +6,67 @@ describe('qrCodes', () => {
[ [
'foo.com', 'foo.com',
{ size: 530, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, { size: 530, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: true, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false },
'foo.com/qr-code/530?format=svg', 'foo.com/qr-code/530?format=svg',
], ],
[ [
'foo.com', 'foo.com',
{ size: 530, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, { size: 530, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: true, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false },
'foo.com/qr-code/530?format=png', 'foo.com/qr-code/530?format=png',
], ],
[ [
'bar.io', 'bar.io',
{ size: 870, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, { size: 870, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: false, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, { useSizeInPath: false, marginIsSupported: false, errorCorrectionIsSupported: false },
'bar.io/qr-code?size=870', 'bar.io/qr-code?size=870&format=svg',
], ],
[ [
'bar.io', 'bar.io',
{ size: 200, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, { size: 200, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: false, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, { useSizeInPath: false, marginIsSupported: false, errorCorrectionIsSupported: false },
'bar.io/qr-code?size=200&format=png', 'bar.io/qr-code?size=200&format=png',
], ],
[ [
'bar.io', 'bar.io',
{ size: 200, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, { size: 200, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: false, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, { useSizeInPath: false, marginIsSupported: false, errorCorrectionIsSupported: false },
'bar.io/qr-code?size=200&format=svg', 'bar.io/qr-code?size=200&format=svg',
], ],
[ [
'foo.net', 'foo.net',
{ size: 480, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, { size: 480, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false },
'foo.net/qr-code/480', 'foo.net/qr-code/480?format=png',
], ],
[ [
'foo.net', 'foo.net',
{ size: 480, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, { size: 480, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false },
'foo.net/qr-code/480', 'foo.net/qr-code/480?format=svg',
], ],
[ [
'shlink.io', 'shlink.io',
{ size: 123, format: 'svg' as QrCodeFormat, margin: 10, errorCorrection: 'L' as QrErrorCorrection }, { size: 123, format: 'svg' as QrCodeFormat, margin: 10, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false },
'shlink.io/qr-code/123', 'shlink.io/qr-code/123?format=svg',
], ],
[ [
'shlink.io', 'shlink.io',
{ size: 456, format: 'png' as QrCodeFormat, margin: 10, errorCorrection: 'L' as QrErrorCorrection }, { size: 456, format: 'png' as QrCodeFormat, margin: 10, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: false }, { useSizeInPath: true, marginIsSupported: true, errorCorrectionIsSupported: false },
'shlink.io/qr-code/456?format=png&margin=10', 'shlink.io/qr-code/456?format=png&margin=10',
], ],
[ [
'shlink.io', 'shlink.io',
{ size: 456, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, { size: 456, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection },
{ useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: false }, { useSizeInPath: true, marginIsSupported: true, errorCorrectionIsSupported: false },
'shlink.io/qr-code/456?format=png', 'shlink.io/qr-code/456?format=png',
], ],
[ [
'shlink.io', 'shlink.io',
{ size: 456, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'H' as QrErrorCorrection }, { size: 456, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'H' as QrErrorCorrection },
{ useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: true }, { useSizeInPath: true, marginIsSupported: true, errorCorrectionIsSupported: true },
'shlink.io/qr-code/456?format=png&errorCorrection=H', 'shlink.io/qr-code/456?format=png&errorCorrection=H',
], ],
])('builds expected URL based in params', (shortUrl, options, capabilities, expectedUrl) => { ])('builds expected URL based in params', (shortUrl, options, capabilities, expectedUrl) => {