mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Merge pull request #237 from acelaya-forks/feature/short-code-length
Feature/short code length
This commit is contained in:
commit
ab2f311bb7
9 changed files with 143 additions and 118 deletions
|
@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
* [#213](https://github.com/shlinkio/shlink-web-client/issues/213) The versions of both shlink-web-client and currently consumed Shlink server are now displayed in the footer.
|
||||
* [#221](https://github.com/shlinkio/shlink-web-client/issues/221) Improved how servers are handled, displaying meaningful errors when a not-found or a not-reachable server is tried to be loaded.
|
||||
* [#226](https://github.com/shlinkio/shlink-web-client/issues/226) Created servers can now be edited.
|
||||
* [#234](https://github.com/shlinkio/shlink-web-client/issues/234) Allowed short code length to be edited on any new short RUL when suing Shlink 2.1 or higher.
|
||||
|
||||
#### Changed
|
||||
|
||||
|
|
20
package-lock.json
generated
20
package-lock.json
generated
|
@ -13894,9 +13894,9 @@
|
|||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.10.2",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz",
|
||||
"integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==",
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
|
||||
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
|
@ -14106,14 +14106,14 @@
|
|||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "16.10.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz",
|
||||
"integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==",
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
|
||||
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.16.2"
|
||||
"scheduler": "^0.19.1"
|
||||
}
|
||||
},
|
||||
"react-error-overlay": {
|
||||
|
@ -15279,9 +15279,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz",
|
||||
"integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==",
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
|
||||
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
|
|
|
@ -43,13 +43,13 @@
|
|||
"prop-types": "^15.7.2",
|
||||
"qs": "^6.9.0",
|
||||
"ramda": "^0.26.1",
|
||||
"react": "^16.10.2",
|
||||
"react": "^16.13.1",
|
||||
"react-autosuggest": "^9.4.3",
|
||||
"react-chartjs-2": "^2.8.0",
|
||||
"react-color": "^2.17.3",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-datepicker": "~1.5.0",
|
||||
"react-dom": "^16.10.2",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-external-link": "^1.0.0",
|
||||
"react-leaflet": "^2.4.0",
|
||||
"react-moment": "^0.9.5",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { serverType } from '../prop-types';
|
||||
import { compareVersions } from '../../utils/helpers/version';
|
||||
import { versionMatch } from '../../utils/helpers/version';
|
||||
|
||||
const propTypes = {
|
||||
minVersion: PropTypes.string,
|
||||
|
@ -16,10 +16,9 @@ const ForServerVersion = ({ minVersion, maxVersion, selectedServer, children })
|
|||
}
|
||||
|
||||
const { version } = selectedServer;
|
||||
const matchesMinVersion = !minVersion || compareVersions(version, '>=', minVersion);
|
||||
const matchesMaxVersion = !maxVersion || compareVersions(version, '<=', maxVersion);
|
||||
const matchesVersion = versionMatch(version, { maxVersion, minVersion });
|
||||
|
||||
if (!matchesMinVersion || !matchesMaxVersion) {
|
||||
if (!matchesVersion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,55 +1,52 @@
|
|||
import { faAngleDoubleDown as downIcon, faAngleDoubleUp as upIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { assoc, dissoc, isEmpty, isNil, pipe, replace, trim } from 'ramda';
|
||||
import React from 'react';
|
||||
import { isEmpty, isNil, pipe, replace, trim } from 'ramda';
|
||||
import React, { useState } from 'react';
|
||||
import { Collapse, FormGroup, Input } from 'reactstrap';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import DateInput from '../utils/DateInput';
|
||||
import Checkbox from '../utils/Checkbox';
|
||||
import { serverType } from '../servers/prop-types';
|
||||
import { compareVersions } from '../utils/helpers/version';
|
||||
import { versionMatch } from '../utils/helpers/version';
|
||||
import { hasValue } from '../utils/utils';
|
||||
import { useToggle } from '../utils/helpers/hooks';
|
||||
import { createShortUrlResultType } from './reducers/shortUrlCreation';
|
||||
import UseExistingIfFoundInfoIcon from './UseExistingIfFoundInfoIcon';
|
||||
|
||||
const normalizeTag = pipe(trim, replace(/ /g, '-'));
|
||||
const formatDate = (date) => isNil(date) ? date : date.format();
|
||||
|
||||
const CreateShortUrl = (
|
||||
TagsSelector,
|
||||
CreateShortUrlResult,
|
||||
ForServerVersion
|
||||
) => class CreateShortUrl extends React.Component {
|
||||
static propTypes = {
|
||||
createShortUrl: PropTypes.func,
|
||||
shortUrlCreationResult: createShortUrlResultType,
|
||||
resetCreateShortUrl: PropTypes.func,
|
||||
selectedServer: serverType,
|
||||
};
|
||||
const propTypes = {
|
||||
createShortUrl: PropTypes.func,
|
||||
shortUrlCreationResult: createShortUrlResultType,
|
||||
resetCreateShortUrl: PropTypes.func,
|
||||
selectedServer: serverType,
|
||||
};
|
||||
|
||||
state = {
|
||||
longUrl: '',
|
||||
tags: [],
|
||||
customSlug: undefined,
|
||||
domain: undefined,
|
||||
validSince: undefined,
|
||||
validUntil: undefined,
|
||||
maxVisits: undefined,
|
||||
findIfExists: false,
|
||||
moreOptionsVisible: false,
|
||||
};
|
||||
const CreateShortUrl = (TagsSelector, CreateShortUrlResult, ForServerVersion) => {
|
||||
const CreateShortUrlComp = ({ createShortUrl, shortUrlCreationResult, resetCreateShortUrl, selectedServer }) => {
|
||||
const [ shortUrlCreation, setShortUrlCreation ] = useState({
|
||||
longUrl: '',
|
||||
tags: [],
|
||||
customSlug: undefined,
|
||||
shortCodeLength: undefined,
|
||||
domain: undefined,
|
||||
validSince: undefined,
|
||||
validUntil: undefined,
|
||||
maxVisits: undefined,
|
||||
findIfExists: false,
|
||||
});
|
||||
const [ moreOptionsVisible, toggleMoreOptionsVisible ] = useToggle(false);
|
||||
|
||||
render() {
|
||||
const { createShortUrl, shortUrlCreationResult, resetCreateShortUrl } = this.props;
|
||||
|
||||
const changeTags = (tags) => this.setState({ tags: tags.map(normalizeTag) });
|
||||
const changeTags = (tags) => setShortUrlCreation({ ...shortUrlCreation, tags: tags.map(normalizeTag) });
|
||||
const renderOptionalInput = (id, placeholder, type = 'text', props = {}) => (
|
||||
<FormGroup>
|
||||
<Input
|
||||
id={id}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={this.state[id]}
|
||||
onChange={(e) => this.setState({ [id]: e.target.value })}
|
||||
value={shortUrlCreation[id]}
|
||||
onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, [id]: e.target.value })}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -57,24 +54,25 @@ const CreateShortUrl = (
|
|||
const renderDateInput = (id, placeholder, props = {}) => (
|
||||
<div className="form-group">
|
||||
<DateInput
|
||||
selected={this.state[id]}
|
||||
selected={shortUrlCreation[id]}
|
||||
placeholderText={placeholder}
|
||||
isClearable
|
||||
onChange={(date) => this.setState({ [id]: date })}
|
||||
onChange={(date) => setShortUrlCreation({ ...shortUrlCreation, [id]: date })}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const save = (e) => {
|
||||
e.preventDefault();
|
||||
createShortUrl(pipe(
|
||||
dissoc('moreOptionsVisible'),
|
||||
assoc('validSince', formatDate(this.state.validSince)),
|
||||
assoc('validUntil', formatDate(this.state.validUntil))
|
||||
)(this.state));
|
||||
createShortUrl({
|
||||
...shortUrlCreation,
|
||||
validSince: formatDate(shortUrlCreation.validSince),
|
||||
validUntil: formatDate(shortUrlCreation.validUntil),
|
||||
});
|
||||
};
|
||||
const currentServerVersion = this.props.selectedServer ? this.props.selectedServer.version : '';
|
||||
const disableDomain = isEmpty(currentServerVersion) || compareVersions(currentServerVersion, '<', '1.19.0-beta.1');
|
||||
const currentServerVersion = selectedServer && selectedServer.version;
|
||||
const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' });
|
||||
const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' });
|
||||
|
||||
return (
|
||||
<form onSubmit={save}>
|
||||
|
@ -84,21 +82,30 @@ const CreateShortUrl = (
|
|||
type="url"
|
||||
placeholder="Insert the URL to be shortened"
|
||||
required
|
||||
value={this.state.longUrl}
|
||||
onChange={(e) => this.setState({ longUrl: e.target.value })}
|
||||
value={shortUrlCreation.longUrl}
|
||||
onChange={(e) => setShortUrlCreation({ ...shortUrlCreation, longUrl: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Collapse isOpen={this.state.moreOptionsVisible}>
|
||||
<Collapse isOpen={moreOptionsVisible}>
|
||||
<div className="form-group">
|
||||
<TagsSelector tags={this.state.tags} onChange={changeTags} />
|
||||
<TagsSelector tags={shortUrlCreation.tags} onChange={changeTags} />
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<div className="col-sm-4">
|
||||
{renderOptionalInput('customSlug', 'Custom slug')}
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<div className="col-sm-4">
|
||||
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', {
|
||||
min: 4,
|
||||
disabled: disableShortCodeLength || hasValue(shortUrlCreation.customSlug),
|
||||
...disableShortCodeLength && {
|
||||
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
<div className="col-sm-4">
|
||||
{renderOptionalInput('domain', 'Domain', 'text', {
|
||||
disabled: disableDomain,
|
||||
...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
|
||||
|
@ -107,14 +114,14 @@ const CreateShortUrl = (
|
|||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<div className="col-sm-4">
|
||||
{renderOptionalInput('maxVisits', 'Maximum number of visits allowed', 'number', { min: 1 })}
|
||||
</div>
|
||||
<div className="col-sm-3">
|
||||
{renderDateInput('validSince', 'Enabled since...', { maxDate: this.state.validUntil })}
|
||||
<div className="col-sm-4">
|
||||
{renderDateInput('validSince', 'Enabled since...', { maxDate: shortUrlCreation.validUntil })}
|
||||
</div>
|
||||
<div className="col-sm-3">
|
||||
{renderDateInput('validUntil', 'Enabled until...', { minDate: this.state.validSince })}
|
||||
<div className="col-sm-4">
|
||||
{renderDateInput('validUntil', 'Enabled until...', { minDate: shortUrlCreation.validSince })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -122,8 +129,8 @@ const CreateShortUrl = (
|
|||
<div className="mb-4 text-right">
|
||||
<Checkbox
|
||||
className="mr-2"
|
||||
checked={this.state.findIfExists}
|
||||
onChange={(findIfExists) => this.setState({ findIfExists })}
|
||||
checked={shortUrlCreation.findIfExists}
|
||||
onChange={(findIfExists) => setShortUrlCreation({ ...shortUrlCreation, findIfExists })}
|
||||
>
|
||||
Use existing URL if found
|
||||
</Checkbox>
|
||||
|
@ -133,18 +140,14 @@ const CreateShortUrl = (
|
|||
</Collapse>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => this.setState(({ moreOptionsVisible }) => ({ moreOptionsVisible: !moreOptionsVisible }))}
|
||||
>
|
||||
<FontAwesomeIcon icon={this.state.moreOptionsVisible ? upIcon : downIcon} />
|
||||
<button type="button" className="btn btn-outline-secondary" onClick={toggleMoreOptionsVisible}>
|
||||
<FontAwesomeIcon icon={moreOptionsVisible ? upIcon : downIcon} />
|
||||
|
||||
{this.state.moreOptionsVisible ? 'Less' : 'More'} options
|
||||
{moreOptionsVisible ? 'Less' : 'More'} options
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary float-right"
|
||||
disabled={shortUrlCreationResult.loading || isEmpty(this.state.longUrl)}
|
||||
disabled={shortUrlCreationResult.loading || isEmpty(shortUrlCreation.longUrl)}
|
||||
>
|
||||
{shortUrlCreationResult.loading ? 'Creating...' : 'Create'}
|
||||
</button>
|
||||
|
@ -153,7 +156,11 @@ const CreateShortUrl = (
|
|||
<CreateShortUrlResult {...shortUrlCreationResult} resetCreateShortUrl={resetCreateShortUrl} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
CreateShortUrlComp.propTypes = propTypes;
|
||||
|
||||
return CreateShortUrlComp;
|
||||
};
|
||||
|
||||
export default CreateShortUrl;
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import { compare } from 'compare-versions';
|
||||
import { identity, memoizeWith } from 'ramda';
|
||||
import { hasValue } from '../utils';
|
||||
|
||||
export const compareVersions = (firstVersion, operator, secondVersion) => compare(
|
||||
firstVersion,
|
||||
secondVersion,
|
||||
operator,
|
||||
);
|
||||
export const versionMatch = (versionToMatch, { maxVersion, minVersion }) => {
|
||||
if (!hasValue(versionToMatch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matchesMinVersion = !minVersion || compare(versionToMatch, minVersion, '>=');
|
||||
const matchesMaxVersion = !maxVersion || compare(versionToMatch, maxVersion, '<=');
|
||||
|
||||
return !!(matchesMaxVersion && matchesMinVersion);
|
||||
};
|
||||
|
||||
const versionIsValidSemVer = memoizeWith(identity, (version) => {
|
||||
try {
|
||||
return compareVersions(version, '=', version);
|
||||
return compare(version, version, '=');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import L from 'leaflet';
|
|||
import marker2x from 'leaflet/dist/images/marker-icon-2x.png';
|
||||
import marker from 'leaflet/dist/images/marker-icon.png';
|
||||
import markerShadow from 'leaflet/dist/images/marker-shadow.png';
|
||||
import { range } from 'ramda';
|
||||
import { isEmpty, isNil, range } from 'ramda';
|
||||
|
||||
const TEN_ROUNDING_NUMBER = 10;
|
||||
const DEFAULT_TIMEOUT_DELAY = 2000;
|
||||
|
@ -45,3 +45,4 @@ export const rangeOf = (size, mappingFn, startAt = 1) => range(startAt, size + 1
|
|||
|
||||
export const roundTen = (number) => ceil(number / TEN_ROUNDING_NUMBER) * TEN_ROUNDING_NUMBER;
|
||||
|
||||
export const hasValue = (value) => !isNil(value) && !isEmpty(value);
|
||||
|
|
|
@ -25,43 +25,31 @@ describe('<CreateShortUrl />', () => {
|
|||
createShortUrl.mockReset();
|
||||
});
|
||||
|
||||
it('saves short URL with data set in form controls', (done) => {
|
||||
it('saves short URL with data set in form controls', () => {
|
||||
const validSince = moment('2017-01-01');
|
||||
const validUntil = moment('2017-01-06');
|
||||
|
||||
const urlInput = wrapper.find('.form-control-lg');
|
||||
const tagsInput = wrapper.find(TagsSelector);
|
||||
const customSlugInput = wrapper.find('#customSlug');
|
||||
const domain = wrapper.find('#domain');
|
||||
const maxVisitsInput = wrapper.find('#maxVisits');
|
||||
const dateInputs = wrapper.find(DateInput);
|
||||
const validSinceInput = dateInputs.at(0);
|
||||
const validUntilInput = dateInputs.at(1);
|
||||
wrapper.find('.form-control-lg').simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } });
|
||||
wrapper.find('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]);
|
||||
wrapper.find('#customSlug').simulate('change', { target: { value: 'my-slug' } });
|
||||
wrapper.find('#domain').simulate('change', { target: { value: 'example.com' } });
|
||||
wrapper.find('#maxVisits').simulate('change', { target: { value: '20' } });
|
||||
wrapper.find('#shortCodeLength').simulate('change', { target: { value: 15 } });
|
||||
wrapper.find(DateInput).at(0).simulate('change', validSince);
|
||||
wrapper.find(DateInput).at(1).simulate('change', validUntil);
|
||||
wrapper.find('form').simulate('submit', { preventDefault: identity });
|
||||
|
||||
urlInput.simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } });
|
||||
tagsInput.simulate('change', [ 'tag_foo', 'tag_bar' ]);
|
||||
customSlugInput.simulate('change', { target: { value: 'my-slug' } });
|
||||
domain.simulate('change', { target: { value: 'example.com' } });
|
||||
maxVisitsInput.simulate('change', { target: { value: '20' } });
|
||||
validSinceInput.simulate('change', validSince);
|
||||
validUntilInput.simulate('change', validUntil);
|
||||
|
||||
setImmediate(() => {
|
||||
const form = wrapper.find('form');
|
||||
|
||||
form.simulate('submit', { preventDefault: identity });
|
||||
expect(createShortUrl).toHaveBeenCalledTimes(1);
|
||||
expect(createShortUrl).toHaveBeenCalledWith({
|
||||
longUrl: 'https://long-domain.com/foo/bar',
|
||||
tags: [ 'tag_foo', 'tag_bar' ],
|
||||
customSlug: 'my-slug',
|
||||
domain: 'example.com',
|
||||
validSince: validSince.format(),
|
||||
validUntil: validUntil.format(),
|
||||
maxVisits: '20',
|
||||
findIfExists: false,
|
||||
});
|
||||
done();
|
||||
expect(createShortUrl).toHaveBeenCalledTimes(1);
|
||||
expect(createShortUrl).toHaveBeenCalledWith({
|
||||
longUrl: 'https://long-domain.com/foo/bar',
|
||||
tags: [ 'tag_foo', 'tag_bar' ],
|
||||
customSlug: 'my-slug',
|
||||
domain: 'example.com',
|
||||
validSince: validSince.format(),
|
||||
validUntil: validUntil.format(),
|
||||
maxVisits: '20',
|
||||
findIfExists: false,
|
||||
shortCodeLength: 15,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
23
test/utils/helpers/version.test.js
Normal file
23
test/utils/helpers/version.test.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { versionMatch } from '../../../src/utils/helpers/version';
|
||||
|
||||
describe('version', () => {
|
||||
describe('versionMatch', () => {
|
||||
it.each([
|
||||
[ undefined, {}, false ],
|
||||
[ null, {}, false ],
|
||||
[ '', {}, false ],
|
||||
[[], {}, false ],
|
||||
[ '2.8.3', {}, true ],
|
||||
[ '2.8.3', { minVersion: '2.0.0' }, true ],
|
||||
[ '2.0.0', { minVersion: '2.0.0' }, true ],
|
||||
[ '1.8.0', { maxVersion: '1.8.0' }, true ],
|
||||
[ '1.7.1', { maxVersion: '1.8.0' }, true ],
|
||||
[ '1.7.3', { minVersion: '1.7.0', maxVersion: '1.8.0' }, true ],
|
||||
[ '1.8.3', { minVersion: '2.0.0' }, false ],
|
||||
[ '1.8.3', { maxVersion: '1.8.0' }, false ],
|
||||
[ '1.8.3', { minVersion: '1.7.0', maxVersion: '1.8.0' }, false ],
|
||||
])('properly matches versions based on what is provided', (version, versionConstraints, expected) => {
|
||||
expect(versionMatch(version, versionConstraints)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue