diff --git a/src/servers/data/index.ts b/src/servers/data/index.ts
index 32c8a75a..9d590288 100644
--- a/src/servers/data/index.ts
+++ b/src/servers/data/index.ts
@@ -1,3 +1,5 @@
+import { SemVer } from '../../utils/helpers/version';
+
export interface ServerData {
name: string;
url: string;
@@ -9,7 +11,7 @@ export interface ServerWithId extends ServerData {
}
export interface ReachableServer extends ServerWithId {
- version: string;
+ version: SemVer;
printableVersion: string;
}
diff --git a/src/utils/helpers/version.ts b/src/utils/helpers/version.ts
index a44a0251..e3695eb1 100644
--- a/src/utils/helpers/version.ts
+++ b/src/utils/helpers/version.ts
@@ -2,12 +2,20 @@ import { compare } from 'compare-versions';
import { identity, memoizeWith } from 'ramda';
import { Empty, hasValue } from '../utils';
+type SemVerPatternFragment = `${bigint | '*'}`;
+
+export type SemVerPattern = SemVerPatternFragment
+| `${SemVerPatternFragment}.${SemVerPatternFragment}`
+| `${SemVerPatternFragment}.${SemVerPatternFragment}.${SemVerPatternFragment}`;
+
export interface Versions {
- maxVersion?: string;
- minVersion?: string;
+ maxVersion?: SemVerPattern;
+ minVersion?: SemVerPattern;
}
-export const versionMatch = (versionToMatch: string | Empty, { maxVersion, minVersion }: Versions): boolean => {
+export type SemVer = `${bigint}.${bigint}.${bigint}`;
+
+export const versionMatch = (versionToMatch: SemVer | Empty, { maxVersion, minVersion }: Versions): boolean => {
if (!hasValue(versionToMatch)) {
return false;
}
@@ -18,7 +26,7 @@ export const versionMatch = (versionToMatch: string | Empty, { maxVersion, minVe
return matchesMaxVersion && matchesMinVersion;
};
-const versionIsValidSemVer = memoizeWith(identity, (version: string) => {
+const versionIsValidSemVer = memoizeWith(identity, (version: string): version is SemVerPattern => {
try {
return compare(version, version, '=');
} catch (e) {
diff --git a/test/common/MenuLayout.test.tsx b/test/common/MenuLayout.test.tsx
index 85cbc109..1e7219b5 100644
--- a/test/common/MenuLayout.test.tsx
+++ b/test/common/MenuLayout.test.tsx
@@ -6,6 +6,7 @@ import { Mock } from 'ts-mockery';
import createMenuLayout from '../../src/common/MenuLayout';
import { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../../src/servers/data';
import NoMenuLayout from '../../src/common/NoMenuLayout';
+import { SemVer } from '../../src/utils/helpers/version';
describe('', () => {
const ServerError = jest.fn();
@@ -48,11 +49,11 @@ describe('', () => {
});
it.each([
- [ '2.1.0', 6 ],
- [ '2.2.0', 7 ],
- [ '2.5.0', 7 ],
- [ '2.6.0', 8 ],
- [ '2.7.0', 8 ],
+ [ '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 ],
])('has expected amount of routes based on selected server\'s version', (version, expectedAmountOfRoutes) => {
const selectedServer = Mock.of({ version });
const wrapper = createWrapper(selectedServer).dive();
diff --git a/test/common/ShlinkVersions.test.tsx b/test/common/ShlinkVersions.test.tsx
index 0a97010f..03af9357 100644
--- a/test/common/ShlinkVersions.test.tsx
+++ b/test/common/ShlinkVersions.test.tsx
@@ -14,11 +14,11 @@ describe('', () => {
afterEach(() => wrapper?.unmount());
it.each([
- [ '1.2.3', Mock.of({ version: '', printableVersion: 'foo' }), 'v1.2.3', 'foo' ],
- [ 'foo', Mock.of({ version: '', printableVersion: '1.2.3' }), 'latest', '1.2.3' ],
- [ 'latest', Mock.of({ version: '', printableVersion: 'latest' }), 'latest', 'latest' ],
- [ '5.5.0', Mock.of({ version: '', printableVersion: '0.2.8' }), 'v5.5.0', '0.2.8' ],
- [ 'not-semver', Mock.of({ version: '', printableVersion: 'something' }), 'latest', 'something' ],
+ [ '1.2.3', Mock.of({ version: '1.0.0', printableVersion: 'foo' }), 'v1.2.3', 'foo' ],
+ [ 'foo', Mock.of({ version: '1.0.0', printableVersion: '1.2.3' }), 'latest', '1.2.3' ],
+ [ 'latest', Mock.of({ version: '1.0.0', printableVersion: 'latest' }), 'latest', 'latest' ],
+ [ '5.5.0', Mock.of({ version: '1.0.0', printableVersion: '0.2.8' }), 'v5.5.0', '0.2.8' ],
+ [ 'not-semver', Mock.of({ version: '1.0.0', printableVersion: 'some' }), 'latest', 'some' ],
])(
'displays expected versions when selected server is reachable',
(clientVersion, selectedServer, expectedClientVersion, expectedServerVersion) => {
diff --git a/test/servers/helpers/ForServerVersion.test.tsx b/test/servers/helpers/ForServerVersion.test.tsx
index 4aabf23f..2167b3d6 100644
--- a/test/servers/helpers/ForServerVersion.test.tsx
+++ b/test/servers/helpers/ForServerVersion.test.tsx
@@ -2,11 +2,12 @@ import { mount, ReactWrapper } from 'enzyme';
import { Mock } from 'ts-mockery';
import ForServerVersion from '../../../src/servers/helpers/ForServerVersion';
import { ReachableServer, SelectedServer } from '../../../src/servers/data';
+import { SemVer, SemVerPattern } from '../../../src/utils/helpers/version';
describe('', () => {
let wrapped: ReactWrapper;
- const renderComponent = (selectedServer: SelectedServer, minVersion?: string, maxVersion?: string) => {
+ const renderComponent = (selectedServer: SelectedServer, minVersion?: SemVerPattern, maxVersion?: SemVerPattern) => {
wrapped = mount(
Hello
@@ -19,15 +20,15 @@ describe('', () => {
afterEach(() => wrapped?.unmount());
it('does not render children when current server is empty', () => {
- const wrapped = renderComponent(null, '1');
+ const wrapped = renderComponent(null, '1.*.*');
expect(wrapped.html()).toBeNull();
});
it.each([
- [ '2.0.0', undefined, '1.8.3' ],
- [ undefined, '1.8.0', '1.8.3' ],
- [ '1.7.0', '1.8.0', '1.8.3' ],
+ [ '2.0.0' as SemVer, undefined, '1.8.3' as SemVer ],
+ [ undefined, '1.8.0' as SemVer, '1.8.3' as SemVer ],
+ [ '1.7.0' as SemVer, '1.8.0' as SemVer, '1.8.3' as SemVer ],
])('does not render children when current version does not match requirements', (min, max, version) => {
const wrapped = renderComponent(Mock.of({ version, printableVersion: version }), min, max);
@@ -35,11 +36,11 @@ describe('', () => {
});
it.each([
- [ '2.0.0', undefined, '2.8.3' ],
- [ '2.0.0', undefined, '2.0.0' ],
- [ undefined, '1.8.0', '1.8.0' ],
- [ undefined, '1.8.0', '1.7.1' ],
- [ '1.7.0', '1.8.0', '1.7.3' ],
+ [ '2.0.0' as SemVer, undefined, '2.8.3' as SemVer ],
+ [ '2.0.0' as SemVer, undefined, '2.0.0' as SemVer ],
+ [ undefined, '1.8.0' as SemVer, '1.8.0' as SemVer ],
+ [ undefined, '1.8.0' as SemVer, '1.7.1' as SemVer ],
+ [ '1.7.0' as SemVer, '1.8.0' as SemVer, '1.7.3' as SemVer ],
])('renders children when current version matches requirements', (min, max, version) => {
const wrapped = renderComponent(Mock.of({ version, printableVersion: version }), min, max);
diff --git a/test/short-urls/ShortUrlsTable.test.tsx b/test/short-urls/ShortUrlsTable.test.tsx
index 74a736b3..7164e40e 100644
--- a/test/short-urls/ShortUrlsTable.test.tsx
+++ b/test/short-urls/ShortUrlsTable.test.tsx
@@ -5,6 +5,7 @@ import { ShortUrlsTable as shortUrlsTableCreator } from '../../src/short-urls/Sh
import { OrderableFields, SORTABLE_FIELDS } from '../../src/short-urls/reducers/shortUrlsListParams';
import { ShortUrlsList } from '../../src/short-urls/reducers/shortUrlsList';
import { ReachableServer, SelectedServer } from '../../src/servers/data';
+import { SemVer } from '../../src/utils/helpers/version';
describe('', () => {
let wrapper: ShallowWrapper;
@@ -61,10 +62,10 @@ describe('', () => {
});
it.each([
- [ '2.6.0' ],
- [ '2.6.1' ],
- [ '2.7.0' ],
- [ '3.0.0' ],
+ [ '2.6.0' as SemVer ],
+ [ '2.6.1' as SemVer ],
+ [ '2.7.0' as SemVer ],
+ [ '3.0.0' as SemVer ],
])('should render composed column when server supports title', (version) => {
const wrapper = createWrapper(Mock.of({ version }));
const composedColumn = wrapper.find('table').find('th').at(2);
diff --git a/test/short-urls/helpers/QrCodeModal.test.tsx b/test/short-urls/helpers/QrCodeModal.test.tsx
index d84b5572..b57b8fcc 100644
--- a/test/short-urls/helpers/QrCodeModal.test.tsx
+++ b/test/short-urls/helpers/QrCodeModal.test.tsx
@@ -7,11 +7,12 @@ import { ShortUrl } from '../../../src/short-urls/data';
import { ReachableServer } from '../../../src/servers/data';
import { CopyToClipboardIcon } from '../../../src/utils/CopyToClipboardIcon';
import { DropdownBtn } from '../../../src/utils/DropdownBtn';
+import { SemVer } from '../../../src/utils/helpers/version';
describe('', () => {
let wrapper: ShallowWrapper;
const shortUrl = 'https://doma.in/abc123';
- const createWrapper = (version = '2.6.0') => {
+ const createWrapper = (version: SemVer = '2.6.0') => {
const selectedServer = Mock.of({ version });
wrapper = shallow(
@@ -37,12 +38,12 @@ describe('', () => {
});
it.each([
- [ '2.3.0', 0, '/qr-code/300' ],
- [ '2.4.0', 0, '/qr-code/300?format=png' ],
- [ '2.4.0', 10, '/qr-code/300?format=png' ],
- [ '2.5.0', 0, '/qr-code?size=300&format=png' ],
- [ '2.6.0', 0, '/qr-code?size=300&format=png' ],
- [ '2.6.0', 10, '/qr-code?size=300&format=png&margin=10' ],
+ [ '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.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' ],
])('displays an image with the QR code of the URL', (version, margin, expectedUrl) => {
const wrapper = createWrapper(version);
const formControls = wrapper.find('.form-control-range');
@@ -84,9 +85,9 @@ describe('', () => {
});
it.each([
- [ '2.3.0', 0, 'col-12' ],
- [ '2.4.0', 1, 'col-md-6' ],
- [ '2.6.0', 1, 'col-md-4' ],
+ [ '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' ],
])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => {
const wrapper = createWrapper(version);
const dropdown = wrapper.find(DropdownBtn);