Files
EasyCLIProxyAPI/tests/versionManagement.test.ts
T

173 lines
7.0 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import { readFileSync } from 'node:fs';
import ts from 'typescript';
import {
DEFAULT_VERSION_DOWNLOAD_SOURCE,
displayAppVersion,
} from '../src/pages/VersionManagementPage';
import { coreUpdateAvailable } from '../src/coreUpdate';
import { appUpdateIndicatorState } from '../src/appUpdateModel';
import { createVersionManagementVisitTracker } from '../src/services/versionManagementVisits';
describe('VersionManagement visit checks', () => {
it('checks on every new page visit', () => {
const recordVisit = createVersionManagementVisitTracker();
const checkedVisits: number[] = [];
for (let visitNumber = 1; visitNumber <= 15; visitNumber += 1) {
if (recordVisit({})) checkedVisits.push(visitNumber);
}
expect(checkedVisits).toEqual(Array.from({ length: 15 }, (_, index) => index + 1));
});
it('checks each mounted page once despite repeated effects or re-renders', () => {
const recordVisit = createVersionManagementVisitTracker();
for (let visitNumber = 1; visitNumber <= 10; visitNumber += 1) {
const visit = {};
expect(recordVisit(visit)).toBe(true);
expect(recordVisit(visit)).toBe(false);
expect(recordVisit(visit)).toBe(false);
}
});
it('starts with no recorded visits for a new application session', () => {
const recordVisit = createVersionManagementVisitTracker();
const visit = {};
expect(recordVisit(visit)).toBe(true);
expect(recordVisit(visit)).toBe(false);
const recordNewSessionVisit = createVersionManagementVisitTracker();
expect(recordNewSessionVisit(visit)).toBe(true);
expect(recordNewSessionVisit(visit)).toBe(false);
});
});
describe('VersionManagement update triggers', () => {
const pageSource = ts.createSourceFile(
'VersionManagementPage.tsx',
readFileSync(new URL('../src/pages/VersionManagementPage.tsx', import.meta.url), 'utf8'),
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX,
);
const findCalls = (name: string) => {
const calls: ts.CallExpression[] = [];
const visit = (node: ts.Node) => {
if (ts.isCallExpression(node) && node.expression.getText(pageSource) === name) {
calls.push(node);
}
ts.forEachChild(node, visit);
};
visit(pageSource);
return calls;
};
for (const name of ['checkAppUpdate', 'checkLatest']) {
it(`calls ${name} only from the visit effect and its manual check button`, () => {
const calls = findCalls(name);
expect(calls).toHaveLength(2);
const triggers = calls.map((call) => {
let ancestor: ts.Node | undefined = call.parent;
while (ancestor) {
if (ts.isJsxAttribute(ancestor)) return ancestor.name.getText(pageSource);
if (ts.isCallExpression(ancestor)
&& ancestor.expression.getText(pageSource) === 'useEffect') return 'useEffect';
ancestor = ancestor.parent;
}
return undefined;
});
expect(triggers.sort()).toEqual(['onClick', 'useEffect']);
});
}
it('only changes the saved download source from the source selector handler', () => {
const sourceWrites = findCalls('invoke').filter((call) => {
const command = call.arguments[0];
return command && ts.isStringLiteral(command) && command.text === 'set_download_source';
});
expect(sourceWrites).toHaveLength(1);
let ancestor: ts.Node | undefined = sourceWrites[0];
while (ancestor && !(ts.isVariableDeclaration(ancestor)
&& ancestor.initializer && ts.isArrowFunction(ancestor.initializer))) {
ancestor = ancestor.parent;
}
expect(ancestor && ts.isVariableDeclaration(ancestor)
? ancestor.name.getText(pageSource)
: undefined).toBe('updateVersionSource');
});
});
describe('VersionManagement helper functions', () => {
it('uses GitHub as the fallback before saved download source settings load', () => {
expect(DEFAULT_VERSION_DOWNLOAD_SOURCE).toBe('github');
});
it('formats app versions with v prefix properly', () => {
expect(displayAppVersion('1.0.0')).toBe('v1.0.0');
expect(displayAppVersion('v1.2.3')).toBe('v1.2.3');
expect(displayAppVersion(' 2.3.4 ')).toBe('v2.3.4');
expect(displayAppVersion(' v3.4.5 ')).toBe('v3.4.5');
});
it('resolves update indicators based on availability and processing state', () => {
expect(appUpdateIndicatorState(true, false, false)).toBe('available');
expect(appUpdateIndicatorState(false, true, false)).toBe('available');
expect(appUpdateIndicatorState(true, true, true)).toBe('processing');
expect(appUpdateIndicatorState(false, false, false)).toBeNull();
});
it('compares installed and latest core versions consistently', () => {
expect(coreUpdateAvailable('v6.6.0', '6.6.0')).toBe(false);
expect(coreUpdateAvailable('6.5.0', 'v6.6.0')).toBe(true);
expect(coreUpdateAvailable('v7.2.151', 'v7.2.150')).toBe(false);
expect(coreUpdateAvailable('6.9.0', '6.10.0')).toBe(true);
expect(coreUpdateAvailable('6.10.0', '6.9.0')).toBe(false);
expect(coreUpdateAvailable('6.6.0-rc.2', '6.6.0-rc.10')).toBe(true);
expect(coreUpdateAvailable('6.6.0-rc.1', '6.6.0')).toBe(true);
expect(coreUpdateAvailable('6.6.0', '6.6.0-rc.1')).toBe(false);
expect(coreUpdateAvailable('6.6.0+local', '6.6.0+remote')).toBe(false);
expect(coreUpdateAvailable(null, '6.6.0')).toBe(false);
expect(coreUpdateAvailable('6.5.0', '')).toBe(false);
expect(coreUpdateAvailable('dev', '6.6.0')).toBe(false);
expect(coreUpdateAvailable('6.5.0', 'unknown')).toBe(false);
});
it('does not show an update indicator for an older core release', () => {
const coreHasUpdate = coreUpdateAvailable('v7.2.151', 'v7.2.150');
expect(appUpdateIndicatorState(false, coreHasUpdate, false)).toBeNull();
});
it('follows the semantic version precedence rules', () => {
const orderedVersions = [
'1.0.0-alpha',
'1.0.0-alpha.1',
'1.0.0-alpha.beta',
'1.0.0-beta',
'1.0.0-beta.2',
'1.0.0-beta.11',
'1.0.0-rc.1',
'1.0.0',
'1.0.1',
'1.1.0',
'2.0.0',
];
for (let index = 1; index < orderedVersions.length; index += 1) {
const older = orderedVersions[index - 1];
const newer = orderedVersions[index];
expect(coreUpdateAvailable(older, newer)).toBe(true);
expect(coreUpdateAvailable(newer, older)).toBe(false);
expect(coreUpdateAvailable(newer, newer)).toBe(false);
}
});
it('handles prefixes, whitespace, large identifiers, and invalid versions', () => {
expect(coreUpdateAvailable(' V7.2.150 ', ' v7.2.151 ')).toBe(true);
expect(coreUpdateAvailable('999999999999999999.0.0', '1000000000000000000.0.0')).toBe(true);
expect(coreUpdateAvailable('1.0.0-999999999999999999', '1.0.0-1000000000000000000')).toBe(true);
expect(coreUpdateAvailable('1.0.0+build.1', '1.0.0+build.2')).toBe(false);
expect(coreUpdateAvailable('1.0.0', '1.01.0')).toBe(false);
expect(coreUpdateAvailable('1.0', '1.0.1')).toBe(false);
expect(coreUpdateAvailable('1.0.0.0', '1.0.1')).toBe(false);
});
});