Files
iptvnator/libs/shared/interfaces/src/lib/app-update-channel.util.spec.ts
T
4grayandClaude Fable 5.1 c9272f5635 feat(updater): nightly builds and a stable/nightly update channel
Every master push publishes its artifacts as a prerelease of
4gray/iptvnator-nightly instead of the rolling test-master draft, with a
version of <next patch>-nightly.<commit date>.<run number> applied in
every build job. Settings → About gains an Update channel switch;
AppUpdateService re-points electron-updater per check (feed repository,
allowPrerelease, channel name, allowDowngrade reset) and reads release
notes from the repository the requested version belongs to. Channel
switches are forward-only: a nightly build stays until a newer stable
release exists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 10:16:39 +02:00

98 lines
3.7 KiB
TypeScript

import {
appUpdateReleasesApiUrl,
appUpdateReleasesPageUrl,
appUpdateRepository,
appVersionChannel,
compareAppVersions,
normalizeAppUpdateChannel,
parseAppVersion,
} from './app-update-channel.util';
describe('app update channel util', () => {
it('collapses unknown channels to stable', () => {
expect(normalizeAppUpdateChannel('nightly')).toBe('nightly');
expect(normalizeAppUpdateChannel('stable')).toBe('stable');
expect(normalizeAppUpdateChannel('canary')).toBe('stable');
expect(normalizeAppUpdateChannel(undefined)).toBe('stable');
expect(normalizeAppUpdateChannel(null)).toBe('stable');
});
it('maps each channel to its repository and pages', () => {
expect(appUpdateRepository('stable')).toEqual({
owner: '4gray',
repo: 'iptvnator',
});
expect(appUpdateRepository('nightly')).toEqual({
owner: '4gray',
repo: 'iptvnator-nightly',
});
// /releases/latest never resolves to a prerelease.
expect(appUpdateReleasesPageUrl('stable')).toBe(
'https://github.com/4gray/iptvnator/releases/latest'
);
expect(appUpdateReleasesPageUrl('nightly')).toBe(
'https://github.com/4gray/iptvnator-nightly/releases'
);
expect(appUpdateReleasesApiUrl('nightly')).toBe(
'https://api.github.com/repos/4gray/iptvnator-nightly/releases'
);
});
it('reads the channel a build belongs to off its version', () => {
expect(appVersionChannel('0.23.0')).toBe('stable');
expect(appVersionChannel('v0.23.0')).toBe('stable');
expect(appVersionChannel('0.23.1-nightly.20260915.1234')).toBe(
'nightly'
);
expect(appVersionChannel('v0.23.1-nightly.20260915.1234')).toBe(
'nightly'
);
expect(appVersionChannel('0.23.1-beta.1')).toBe('stable');
expect(appVersionChannel('junk')).toBe('stable');
expect(appVersionChannel(undefined)).toBe('stable');
});
it('parses core and prerelease identifiers', () => {
expect(parseAppVersion('v0.23.1-nightly.20260915.7+abc')).toEqual({
core: [0, 23, 1],
prerelease: ['nightly', '20260915', '7'],
});
expect(parseAppVersion('0.23')).toBeNull();
});
it('orders versions by semver precedence', () => {
expect(compareAppVersions('0.24.0', '0.23.0')).toBe(1);
expect(compareAppVersions('0.23.0', '0.23.0')).toBe(0);
// A nightly sits between the released version and the next patch.
expect(compareAppVersions('0.23.1-nightly.20260915.7', '0.23.0')).toBe(
1
);
expect(compareAppVersions('0.23.1-nightly.20260915.7', '0.23.1')).toBe(
-1
);
expect(compareAppVersions('0.23.1-nightly.20260915.7', '0.24.0')).toBe(
-1
);
// Same day: the run number decides numerically, not lexically.
expect(
compareAppVersions(
'0.23.1-nightly.20260915.1000',
'0.23.1-nightly.20260915.999'
)
).toBe(1);
expect(
compareAppVersions(
'0.23.1-nightly.20260916.1',
'0.23.1-nightly.20260915.999'
)
).toBe(1);
// Fewer identifiers rank lower; numeric identifiers rank below words.
expect(compareAppVersions('0.23.1-nightly', '0.23.1-nightly.1')).toBe(
-1
);
expect(compareAppVersions('0.23.1-1', '0.23.1-alpha')).toBe(-1);
// Junk never looks like an update.
expect(compareAppVersions('junk', '0.23.0')).toBe(0);
});
});