mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
197 lines
5.6 KiB
TypeScript
197 lines
5.6 KiB
TypeScript
import semver from 'semver';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
resolveArchivedVersions,
|
|
ArchivedVersion,
|
|
getMobileBuild,
|
|
getNewVersion,
|
|
ReleaseError,
|
|
} from './release';
|
|
|
|
const mobileBuild = (version: string) =>
|
|
getMobileBuild(semver.parse(version) as semver.SemVer);
|
|
|
|
const archived = (label: string): ArchivedVersion => ({
|
|
label,
|
|
url: `https://docs.${label}.archive.immich.app`,
|
|
});
|
|
|
|
const labels = (versions: ArchivedVersion[]) => versions.map((v) => v.label);
|
|
|
|
describe(getNewVersion.name, () => {
|
|
describe('transitions', () => {
|
|
const valid = [
|
|
{
|
|
name: 'patch',
|
|
items: [['patch', '2.7.5', '2.7.6']],
|
|
},
|
|
{
|
|
name: 'prepatch',
|
|
items: [
|
|
['prepatch', '2.7.5', '2.7.6-rc.0'],
|
|
['prerelease', '2.7.6-rc.0', '2.7.6-rc.1'],
|
|
['release', '2.7.6-rc.1', '2.7.6'],
|
|
],
|
|
},
|
|
{
|
|
name: 'minor',
|
|
items: [['minor', '2.7.5', '2.8.0']],
|
|
},
|
|
{
|
|
name: 'preminor',
|
|
items: [
|
|
['preminor', '2.7.5', '2.8.0-rc.0'],
|
|
['prerelease', '2.8.0-rc.0', '2.8.0-rc.1'],
|
|
['release', '2.8.0-rc.1', '2.8.0'],
|
|
],
|
|
},
|
|
{
|
|
name: 'premajor',
|
|
items: [
|
|
['premajor', '2.7.5', '3.0.0-rc.0'],
|
|
['prerelease', '3.0.0-rc.0', '3.0.0-rc.1'],
|
|
['release', '3.0.0-rc.1', '3.0.0'],
|
|
],
|
|
},
|
|
];
|
|
|
|
for (const group of valid) {
|
|
describe(group.name, () => {
|
|
it.each(group.items)(
|
|
'should allow a $0 from $1 to $2',
|
|
(type, version, next) => {
|
|
expect(getNewVersion(version, type)).toEqual(next);
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
describe('invalid', () => {
|
|
it.each([
|
|
['patch', 'v3.0.0-rc.0'],
|
|
['prepatch', 'v3.0.0-rc.0'],
|
|
['minor', 'v3.0.0-rc.0'],
|
|
['preminor', 'v3.0.0-rc.0'],
|
|
['premajor', 'v3.0.0-rc.0'],
|
|
['prerelease', 'v3.0.0'],
|
|
['release', 'v3.0.0'],
|
|
])('should not allow a $0 on $1', (type, version) => {
|
|
expect(() => getNewVersion(version, type)).toThrow(ReleaseError);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe(resolveArchivedVersions.name, () => {
|
|
beforeEach(() => {
|
|
// silence the "Removed ..." progress logging
|
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
});
|
|
|
|
it('should prepend the new version to the front', () => {
|
|
const result = resolveArchivedVersions([archived('v2.9.0')], '3.0.0');
|
|
|
|
expect(result[0]).toEqual({
|
|
label: 'v3.0.0',
|
|
url: 'https://docs.v3.0.0.archive.immich.app',
|
|
});
|
|
expect(labels(result)).toEqual(['v3.0.0', 'v2.9.0']);
|
|
});
|
|
|
|
it('should handle an empty list', () => {
|
|
expect(labels(resolveArchivedVersions([], '3.0.0'))).toEqual(['v3.0.0']);
|
|
});
|
|
|
|
it('should drop older patch releases of the new version minor', () => {
|
|
const versions = ['v3.0.4', 'v3.0.3', 'v3.0.2'].map(archived);
|
|
|
|
expect(labels(resolveArchivedVersions(versions, '3.0.5'))).toEqual([
|
|
'v3.0.5',
|
|
]);
|
|
});
|
|
|
|
it('should keep the latest patch of each older minor', () => {
|
|
const versions = ['v3.1.2', 'v3.1.1', 'v3.0.9', 'v2.0.0'].map(archived);
|
|
|
|
expect(labels(resolveArchivedVersions(versions, '3.2.0'))).toEqual([
|
|
'v3.2.0',
|
|
'v3.1.2',
|
|
'v3.0.9',
|
|
'v2.0.0',
|
|
]);
|
|
});
|
|
|
|
it('should keep an older minor when bumping a patch', () => {
|
|
const versions = ['v3.0.4', 'v3.0.3', 'v2.9.1', 'v2.9.0', 'v1.5.0'].map(
|
|
archived,
|
|
);
|
|
|
|
expect(labels(resolveArchivedVersions(versions, '3.0.5'))).toEqual([
|
|
'v3.0.5',
|
|
'v2.9.1',
|
|
'v1.5.0',
|
|
]);
|
|
});
|
|
|
|
it('should replace a prerelease with its release', () => {
|
|
const versions = ['v3.0.0-rc.2', 'v3.0.0-rc.1', 'v2.9.0'].map(archived);
|
|
|
|
expect(labels(resolveArchivedVersions(versions, '3.0.0'))).toEqual([
|
|
'v3.0.0',
|
|
'v2.9.0',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe(getMobileBuild.name, () => {
|
|
it('should encode the version', () => {
|
|
expect(mobileBuild('3.1.0')).toBe(3_010_099);
|
|
expect(mobileBuild('3.1.4')).toBe(3_010_499);
|
|
expect(mobileBuild('3.2.0-rc.0')).toBe(3_020_000);
|
|
expect(mobileBuild('3.2.0-rc.3')).toBe(3_020_003);
|
|
});
|
|
|
|
it('should be above the last hand-maintained build number', () => {
|
|
expect(mobileBuild('3.1.0')).toBeGreaterThan(3057);
|
|
});
|
|
|
|
it('should stay under the play store limit', () => {
|
|
expect(mobileBuild('99.99.99')).toBeLessThan(2_100_000_000);
|
|
});
|
|
|
|
it('should increase across a release cycle', () => {
|
|
const versions = [
|
|
'3.1.0',
|
|
'3.1.1',
|
|
'3.2.0-rc.0',
|
|
'3.2.0-rc.1',
|
|
'3.2.0',
|
|
'3.2.1',
|
|
'3.3.0-rc.0',
|
|
'4.0.0-rc.0',
|
|
'4.0.0',
|
|
];
|
|
|
|
const builds = versions.map((version) => mobileBuild(version));
|
|
expect(builds).toEqual([...builds].sort((a, b) => a - b));
|
|
expect(new Set(builds).size).toBe(builds.length);
|
|
});
|
|
|
|
it('should rank a release above its own candidates', () => {
|
|
expect(mobileBuild('3.2.0')).toBeGreaterThan(mobileBuild('3.2.0-rc.98'));
|
|
});
|
|
|
|
it('should rank a patch on an older line below the newer line', () => {
|
|
expect(mobileBuild('3.1.5')).toBeLessThan(mobileBuild('3.2.0'));
|
|
});
|
|
|
|
it('should refuse versions it cannot encode', () => {
|
|
expect(() => mobileBuild('100.0.0')).toThrow('Cannot derive');
|
|
expect(() => mobileBuild('3.100.0')).toThrow('Cannot derive');
|
|
expect(() => mobileBuild('3.1.100')).toThrow('Cannot derive');
|
|
expect(() => mobileBuild('3.1.0-rc.99')).toThrow('Cannot derive');
|
|
expect(() => mobileBuild('3.1.0-beta')).toThrow('Cannot derive');
|
|
expect(() => mobileBuild('3.1.0-rc.-1')).toThrow('Cannot derive');
|
|
expect(() => mobileBuild('3.1.0-rc.x')).toThrow('Cannot derive');
|
|
});
|
|
});
|