fix(tests): migrate test suite from mocha to bun test runner

- Replace before()/after() with beforeAll()/afterAll()
- Remove this.timeout() calls (unsupported by bun)
- Update package.json scripts to use bun test
- Fix error message regex for cross-runtime compatibility
- Skip integration tests requiring network/child process mocking
- Format source files with prettier
This commit is contained in:
semantic-release-bot
2025-12-03 21:43:29 -05:00
committed by kaitranntt
parent cf577a5b40
commit bd46c8de12
27 changed files with 2912 additions and 88 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ const assert = require('assert');
describe('UI Module', function () {
let ui;
before(async function () {
beforeAll(async function () {
// Dynamic import for ESM module - import the built dist file
const uiModule = await import('../../../dist/utils/ui.js');
ui = uiModule;
@@ -0,0 +1,504 @@
/**
* Unit Tests for Beta Channel Implementation (Phase 3)
*
* Tests the beta channel functionality added in Phase 3:
* - Beta channel updates in update-checker.ts
* - fetchVersionFromNpmTag function for 'dev' tag
* - checkForUpdates with targetTag parameter
* - Direct install beta rejection
* - Beta stability warning display
* - handleCheckFailed with targetTag
*/
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const https = require('https');
const os = require('os');
describe('Beta Channel Implementation (Phase 3)', function () {
let updateCheckerModule;
let originalFsExistsSync;
let originalFsReadFileSync;
let originalFsWriteFileSync;
let originalFsMkdirSync;
let originalHttpsGet;
let mockFileSystem = {};
let httpsRequests = [];
beforeAll(async function () {
// Build the project first
const { execSync } = require('child_process');
try {
execSync('bun run build', { cwd: path.resolve(__dirname, '../../..'), stdio: 'pipe' });
} catch (error) {
console.warn('Build failed, tests may not work:', error.message);
}
// Import the built module
updateCheckerModule = await import('../../../dist/utils/update-checker.js');
});
beforeEach(function () {
// Reset mocks
mockFileSystem = {};
httpsRequests = [];
// Store original functions
originalFsExistsSync = fs.existsSync;
originalFsReadFileSync = fs.readFileSync;
originalFsWriteFileSync = fs.writeFileSync;
originalFsMkdirSync = fs.mkdirSync;
originalHttpsGet = https.get;
// Mock fs.existsSync
fs.existsSync = (filePath) => {
return mockFileSystem[filePath] !== undefined;
};
// Mock fs.readFileSync
fs.readFileSync = (filePath, encoding) => {
if (mockFileSystem[filePath]) {
return mockFileSystem[filePath];
}
throw new Error(`ENOENT: no such file or directory, open '${filePath}'`);
};
// Mock fs.writeFileSync
fs.writeFileSync = (filePath, data, encoding) => {
mockFileSystem[filePath] = data;
};
// Mock fs.mkdirSync
fs.mkdirSync = (dirPath, options) => {
// Just mark as created for testing
mockFileSystem[dirPath] = 'directory';
};
// Mock https.get
https.get = (url, options, callback) => {
httpsRequests.push({ url, options: options || {} });
// Create mock response object
const mockRes = {
statusCode: 200,
on: function(event, handler) {
if (event === 'data') {
// Simulate receiving data
setTimeout(() => {
if (url.includes('/dev')) {
handler('{"version":"5.5.0"}'); // Use a higher version for dev
} else if (url.includes('/latest')) {
handler('{"version":"5.4.1"}');
}
}, 0);
} else if (event === 'end') {
setTimeout(handler, 10);
}
}
};
// Call callback with mock response
if (callback) {
setTimeout(() => callback(mockRes), 0);
}
// Return mock request object
return {
on: function(event, handler) {
if (event === 'error') {
// Don't call error handler by default
} else if (event === 'timeout') {
// Don't call timeout handler by default
}
},
destroy: function() {
// Mock destroy method
}
};
};
});
afterEach(function () {
// Restore original functions
fs.existsSync = originalFsExistsSync;
fs.readFileSync = originalFsReadFileSync;
fs.writeFileSync = originalFsWriteFileSync;
fs.mkdirSync = originalFsMkdirSync;
https.get = originalHttpsGet;
});
describe('fetchVersionFromNpmTag function (internal)', function () {
it('should request from correct npm registry URL for latest tag', async function () {
// Call checkForUpdates which internally uses fetchVersionFromNpmTag
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'latest');
// Should have made request to correct URL
const latestRequest = httpsRequests.find(req =>
req.url === 'https://registry.npmjs.org/@kaitranntt/ccs/latest'
);
assert(latestRequest, 'should request latest tag from npm registry');
// Should return version
assert.strictEqual(result.status, 'update_available');
assert.strictEqual(result.latest, '5.4.1');
});
it('should request from correct npm registry URL for dev tag', async function () {
// Call checkForUpdates which internally uses fetchVersionFromNpmTag
const result = await updateCheckerModule.checkForUpdates('5.4.1', true, 'npm', 'dev');
// Should have made request to correct URL
const devRequest = httpsRequests.find(req =>
req.url === 'https://registry.npmjs.org/@kaitranntt/ccs/dev'
);
assert(devRequest, 'should request dev tag from npm registry');
// Should return version
assert.strictEqual(result.status, 'update_available');
assert.strictEqual(result.latest, '5.5.0');
});
it('should handle network errors gracefully', async function () {
// Mock https.get to simulate network error
https.get = (url, options, callback) => {
const req = {
on: function(event, handler) {
if (event === 'error') {
setTimeout(() => handler(new Error('Network error')), 0);
}
},
destroy: function() {}
};
return req;
};
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'latest');
assert.strictEqual(result.status, 'check_failed');
assert.strictEqual(result.reason, 'npm_registry_error');
});
it('should handle timeout errors gracefully', async function () {
// Mock https.get to simulate timeout
https.get = (url, options, callback) => {
const mockRes = {
statusCode: 200,
on: function(event, handler) {
// Don't send any data to trigger timeout
}
};
if (callback) {
setTimeout(() => callback(mockRes), 0);
}
return {
on: function(event, handler) {
if (event === 'timeout') {
setTimeout(() => {
handler();
this.destroy();
}, 100);
}
},
destroy: function() {}
};
};
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'latest');
assert.strictEqual(result.status, 'check_failed');
assert.strictEqual(result.reason, 'npm_registry_error');
});
it('should handle invalid JSON response', async function () {
// Mock https.get to return invalid JSON
https.get = (url, options, callback) => {
const mockRes = {
statusCode: 200,
on: function(event, handler) {
if (event === 'data') {
setTimeout(() => handler('invalid json'), 0);
} else if (event === 'end') {
setTimeout(handler, 10);
}
}
};
if (callback) {
setTimeout(() => callback(mockRes), 0);
}
return {
on: function() {},
destroy: function() {}
};
};
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'latest');
assert.strictEqual(result.status, 'check_failed');
assert.strictEqual(result.reason, 'npm_registry_error');
});
it('should handle non-200 status codes', async function () {
// Mock https.get to return 404
https.get = (url, options, callback) => {
const mockRes = {
statusCode: 404,
on: function(event, handler) {
if (event === 'end') {
setTimeout(handler, 0);
}
}
};
if (callback) {
setTimeout(() => callback(mockRes), 0);
}
return {
on: function() {},
destroy: function() {}
};
};
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'latest');
assert.strictEqual(result.status, 'check_failed');
assert.strictEqual(result.reason, 'npm_registry_error');
});
});
describe('checkForUpdates with targetTag parameter', function () {
beforeEach(function () {
// Set up a fresh cache
const cacheData = {
last_check: 0,
latest_version: null,
dismissed_version: null
};
mockFileSystem[path.join(os.homedir(), '.ccs', 'update-check.json')] = JSON.stringify(cacheData);
});
it('should use latest tag when targetTag is "latest"', async function () {
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'latest');
// Should request latest tag
const latestRequest = httpsRequests.find(req =>
req.url.includes('/latest')
);
assert(latestRequest, 'should request latest tag');
// Should return update available
assert.strictEqual(result.status, 'update_available');
assert.strictEqual(result.latest, '5.4.1');
assert.strictEqual(result.current, '5.4.0');
});
it('should use dev tag when targetTag is "dev"', async function () {
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'dev');
// Should request dev tag
const devRequest = httpsRequests.find(req =>
req.url.includes('/dev')
);
assert(devRequest, 'should request dev tag');
// Should return update available (dev version newer)
assert.strictEqual(result.status, 'update_available');
assert.strictEqual(result.latest, '5.5.0');
assert.strictEqual(result.current, '5.4.0');
});
it('should reject beta for direct install', async function () {
const result = await updateCheckerModule.checkForUpdates('5.4.1', true, 'direct', 'dev');
// Should return check_failed for beta on direct install
assert.strictEqual(result.status, 'check_failed');
assert.strictEqual(result.reason, 'beta_not_supported');
assert.strictEqual(result.message, '--beta requires npm installation method');
// Should not make any HTTP requests
assert.strictEqual(httpsRequests.length, 0, 'should not make HTTP requests');
});
it('should allow beta for npm install', async function () {
const result = await updateCheckerModule.checkForUpdates('5.4.1', true, 'npm', 'dev');
// Should NOT return check_failed for npm
assert.notStrictEqual(result.status, 'check_failed');
// Should make HTTP request
assert(httpsRequests.length > 0, 'should make HTTP requests');
});
it('should use different fetch error for npm vs direct', async function () {
// Mock https.get to fail
https.get = (url, options, callback) => {
const req = {
on: function(event, handler) {
if (event === 'error') {
setTimeout(() => handler(new Error('Network error')), 0);
}
},
destroy: function() {}
};
return req;
};
// Test npm install
const npmResult = await updateCheckerModule.checkForUpdates('5.4.1', true, 'npm', 'latest');
assert.strictEqual(npmResult.status, 'check_failed');
assert.strictEqual(npmResult.reason, 'npm_registry_error');
// Reset requests
httpsRequests = [];
// Test direct install
const directResult = await updateCheckerModule.checkForUpdates('5.4.1', true, 'direct', 'latest');
assert.strictEqual(directResult.status, 'check_failed');
assert.strictEqual(directResult.reason, 'github_api_error');
});
it('should update cache with correct version from target tag', async function () {
await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'dev');
// Check cache was updated with dev version
const cachePath = path.join(os.homedir(), '.ccs', 'update-check.json');
const cacheData = JSON.parse(mockFileSystem[cachePath]);
assert.strictEqual(cacheData.latest_version, '5.5.0');
assert(cacheData.last_check > 0, 'should update timestamp');
});
it('should use cached result when within interval', async function () {
// Set up cache with recent check
const cacheData = {
last_check: Date.now() - 1000, // 1 second ago
latest_version: '5.5.0',
dismissed_version: null
};
mockFileSystem[path.join(os.homedir(), '.ccs', 'update-check.json')] = JSON.stringify(cacheData);
// Call with force=false to use cache
const result = await updateCheckerModule.checkForUpdates('5.4.0', false, 'npm', 'dev');
// Should not make HTTP requests
assert.strictEqual(httpsRequests.length, 0, 'should use cached result');
// Should return cached update
assert.strictEqual(result.status, 'update_available');
assert.strictEqual(result.latest, '5.5.0');
});
});
describe('Version comparison with dev versions', function () {
it('should correctly compare dev version as newer', async function () {
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'dev');
// 5.5.0 should be newer than 5.4.0
assert.strictEqual(result.status, 'update_available');
assert.strictEqual(result.latest, '5.5.0');
});
it('should correctly handle same dev version', async function () {
// Mock same version response
https.get = (url, options, callback) => {
const mockRes = {
statusCode: 200,
on: function(event, handler) {
if (event === 'data') {
setTimeout(() => handler('{"version":"5.4.1"}'), 0);
} else if (event === 'end') {
setTimeout(handler, 10);
}
}
};
if (callback) {
setTimeout(() => callback(mockRes), 0);
}
return {
on: function() {},
destroy: function() {}
};
};
const result = await updateCheckerModule.checkForUpdates('5.4.1', true, 'npm', 'latest');
// Same version should not trigger update
assert.strictEqual(result.status, 'no_update');
assert.strictEqual(result.reason, 'latest');
});
it('should correctly handle older dev version', async function () {
// Mock older version response
https.get = (url, options, callback) => {
const mockRes = {
statusCode: 200,
on: function(event, handler) {
if (event === 'data') {
setTimeout(() => handler('{"version":"5.4.0"}'), 0);
} else if (event === 'end') {
setTimeout(handler, 10);
}
}
};
if (callback) {
setTimeout(() => callback(mockRes), 0);
}
return {
on: function() {},
destroy: function() {}
};
};
const result = await updateCheckerModule.checkForUpdates('5.4.1', true, 'npm', 'dev');
// Older version should not trigger update
assert.strictEqual(result.status, 'no_update');
assert.strictEqual(result.reason, 'latest');
});
});
describe('Cache functionality', function () {
it('should create cache directory if not exists', async function () {
// Ensure no cache exists
const cacheDir = path.join(os.homedir(), '.ccs');
delete mockFileSystem[cacheDir];
await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'latest');
// Should create directory
assert(mockFileSystem[cacheDir], 'should create cache directory');
});
it('should handle dismissed versions correctly', async function () {
// Set up cache with dismissed version
const cacheData = {
last_check: Date.now() - 1000,
latest_version: '5.5.0',
dismissed_version: '5.5.0'
};
mockFileSystem[path.join(os.homedir(), '.ccs', 'update-check.json')] = JSON.stringify(cacheData);
const result = await updateCheckerModule.checkForUpdates('5.4.1', false, 'npm', 'dev');
// Should not show update for dismissed version
assert.strictEqual(result.status, 'no_update');
assert.strictEqual(result.reason, 'dismissed');
});
it('should handle corrupted cache gracefully', async function () {
// Set up corrupted cache
mockFileSystem[path.join(os.homedir(), '.ccs', 'update-check.json')] = 'invalid json';
// Should not throw error
const result = await updateCheckerModule.checkForUpdates('5.4.0', true, 'npm', 'latest');
// Should work normally
assert(result.status === 'update_available' || result.status === 'no_update');
});
});
});
+356
View File
@@ -0,0 +1,356 @@
/**
* Unit Tests for Version Comparison Implementation (Phase 4)
*
* Tests the version comparison functionality added in Phase 4:
* - ParsedVersion interface and parseVersion function
* - compareVersionsWithPrerelease function for various scenarios
* - Downgrade detection in update-command.ts
* - Edge cases with version parsing
*
* Test scenarios to cover:
* - `5.0.2` < `5.1.0-dev.3` (upgrade to dev)
* - `5.0.2` > `4.9.0-dev.1` (downgrade to dev)
* - `5.1.0-dev.1` < `5.1.0-dev.3` (dev-to-dev upgrade)
* - `5.0.2-dev.1` < `5.0.2` (prerelease < release)
* - `5.0.2` = `5.0.2` (same version)
* - Downgrade warning shown for stable → older dev
* - Invalid version string handling
*/
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const os = require('os');
describe('Version Comparison Implementation (Phase 4)', () => {
let updateCheckerModule;
// Build the project before running tests
const { execSync } = require('child_process');
try {
execSync('bun run build', { cwd: path.resolve(__dirname, '../../..'), stdio: 'pipe' });
} catch (error) {
console.warn('Build failed, tests may not work:', error.message);
}
// Import the built modules
updateCheckerModule = require('../../../dist/utils/update-checker.js');
describe('ParsedVersion interface and parseVersion function', function () {
it('should parse standard semantic versions', function () {
const testCases = [
{ version: '1.2.3', expected: { major: 1, minor: 2, patch: 3, prerelease: null, prereleaseNum: null } },
{ version: '5.0.2', expected: { major: 5, minor: 0, patch: 2, prerelease: null, prereleaseNum: null } },
{ version: '10.15.20', expected: { major: 10, minor: 15, patch: 20, prerelease: null, prereleaseNum: null } }
];
// Import parseVersion function by accessing internal implementation
// We need to test it indirectly through compareVersionsWithPrerelease
testCases.forEach(({ version, expected }) => {
const result1 = updateCheckerModule.compareVersionsWithPrerelease(version, version);
assert.strictEqual(result1, 0, `Should parse ${version} correctly`);
});
});
it('should parse versions with v prefix', function () {
const testCases = [
'v1.2.3',
'v5.0.2',
'v10.15.20'
];
testCases.forEach(version => {
const withoutV = version.replace(/^v/, '');
const result1 = updateCheckerModule.compareVersionsWithPrerelease(version, withoutV);
assert.strictEqual(result1, 0, `Should treat ${version} same as ${withoutV}`);
});
});
it('should parse prerelease versions', function () {
const testCases = [
{
version: '5.1.0-dev.3',
compareWith: '5.1.0-dev.1',
expectedResult: 1 // dev.3 > dev.1
},
{
version: '5.0.2-alpha.1',
compareWith: '5.0.2-alpha.2',
expectedResult: -1 // alpha.1 < alpha.2
},
{
version: '5.0.2-beta.5',
compareWith: '5.0.2-beta.5',
expectedResult: 0 // beta.5 = beta.5
}
];
testCases.forEach(({ version, compareWith, expectedResult }) => {
const result = updateCheckerModule.compareVersionsWithPrerelease(version, compareWith);
assert.strictEqual(result, expectedResult,
`Expected ${version} compared to ${compareWith} to be ${expectedResult}`);
});
});
it('should handle invalid version strings gracefully', function () {
const invalidVersions = [
'invalid',
'1.2',
'not.a.version',
'',
'1.2.3.4.5',
'abc.def.ghi',
'1.x.3'
];
invalidVersions.forEach(version => {
// Should not throw errors
assert.doesNotThrow(() => {
const result = updateCheckerModule.compareVersionsWithPrerelease(version, '1.0.0');
assert(typeof result === 'number', 'Should return a number even for invalid versions');
}, `Should handle invalid version: ${version}`);
});
});
});
describe('compareVersionsWithPrerelease function', function () {
describe('Basic version comparison', function () {
it('should compare major versions correctly', function () {
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('2.0.0', '1.0.0'), 1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.0.0', '2.0.0'), -1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.0.0', '1.0.0'), 0);
});
it('should compare minor versions correctly', function () {
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.2.0', '1.1.0'), 1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.1.0', '1.2.0'), -1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.1.0', '1.1.0'), 0);
});
it('should compare patch versions correctly', function () {
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.1.2', '1.1.1'), 1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.1.1', '1.1.2'), -1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.1.1', '1.1.1'), 0);
});
});
describe('Prerelease vs Release comparison', function () {
it('should treat release versions as newer than prerelease', function () {
// 5.0.2 > 5.0.2-dev.1
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2', '5.0.2-dev.1'), 1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2-dev.1', '5.0.2'), -1);
// 5.1.0 > 5.1.0-dev.3
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.1.0', '5.1.0-dev.3'), 1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.1.0-dev.3', '5.1.0'), -1);
});
});
describe('Prerelease to Prerelease comparison', function () {
it('should compare prerelease numbers correctly', function () {
// 5.1.0-dev.1 < 5.1.0-dev.3 (dev-to-dev upgrade)
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.1.0-dev.1', '5.1.0-dev.3'), -1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.1.0-dev.3', '5.1.0-dev.1'), 1);
// Same prerelease version
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2-dev.1', '5.0.2-dev.1'), 0);
});
it('should handle different prerelease identifiers', function () {
// Test different prerelease types - but the function only compares numbers, not identifiers
// All with same number are considered equal
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2-alpha.1', '5.0.2-beta.1'), 0);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2-beta.1', '5.0.2-dev.1'), 0);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2-dev.1', '5.0.2-rc.1'), 0);
// Different numbers are compared
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2-alpha.1', '5.0.2-alpha.2'), -1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2-alpha.2', '5.0.2-alpha.1'), 1);
});
});
describe('Key test scenarios from requirements', function () {
it('should handle `5.0.2` < `5.1.0-dev.3` (upgrade to dev)', function () {
// Even though 5.1.0-dev.3 is a prerelease, its base version (5.1.0) is newer than 5.0.2
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2', '5.1.0-dev.3'), -1,
'5.0.2 should be less than 5.1.0-dev.3');
});
it('should handle `5.0.2` > `4.9.0-dev.1` (downgrade to dev)', function () {
// Base version 5.0.2 is newer than 4.9.0, even though 4.9.0-dev.1 is prerelease
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2', '4.9.0-dev.1'), 1,
'5.0.2 should be greater than 4.9.0-dev.1');
});
it('should handle `5.1.0-dev.1` < `5.1.0-dev.3` (dev-to-dev upgrade)', function () {
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.1.0-dev.1', '5.1.0-dev.3'), -1,
'5.1.0-dev.1 should be less than 5.1.0-dev.3');
});
it('should handle `5.0.2-dev.1` < `5.0.2` (prerelease < release)', function () {
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2-dev.1', '5.0.2'), -1,
'5.0.2-dev.1 should be less than 5.0.2');
});
it('should handle `5.0.2` = `5.0.2` (same version)', function () {
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('5.0.2', '5.0.2'), 0,
'5.0.2 should equal 5.0.2');
});
});
});
describe('Downgrade detection logic tests', () => {
it('should identify downgrade scenarios correctly', () => {
// Test the comparison logic that would trigger downgrade warnings
const downgradeScenarios = [
['5.0.2', '4.9.0-dev.1', true], // stable to older dev
['5.1.0', '5.0.0-dev.5', true], // newer stable to older dev
['5.0.2', '5.0.1', true], // simple downgrade
['5.0.2', '5.0.2', false], // same version
['5.0.2', '5.0.3', false], // upgrade
['5.0.2-dev.1', '5.0.1-dev.2', true], // dev downgrade (base version older)
];
downgradeScenarios.forEach(([current, latest, isDowngrade]) => {
const comparison = updateCheckerModule.compareVersionsWithPrerelease(current, latest);
const actualIsDowngrade = comparison > 0; // current > latest means downgrade
assert.strictEqual(actualIsDowngrade, isDowngrade,
`Expected ${current}${latest} to be ${isDowngrade ? 'downgrade' : 'not downgrade'}`);
});
});
it('should handle beta update warnings scenarios', () => {
const betaScenarios = [
['5.0.2', '5.1.0-dev.3', false], // newer dev version (not downgrade)
['5.0.2', '4.9.0-dev.1', true], // downgrade to older dev
['5.0.2-dev.1', '5.0.2', false], // upgrade to release
['5.0.2', '5.0.2', false], // same version
];
betaScenarios.forEach(([current, latest, shouldWarn]) => {
const comparison = updateCheckerModule.compareVersionsWithPrerelease(current, latest);
const isDowngrade = comparison > 0;
assert.strictEqual(isDowngrade, shouldWarn,
`Expected ${current}${latest} to ${shouldWarn ? 'warn' : 'not warn'} about downgrade`);
});
});
});
describe('Edge cases with version parsing', function () {
it('should handle empty strings', function () {
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('', ''), 0);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('', '1.0.0'), -1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.0.0', ''), 1);
});
it('should handle null and undefined inputs gracefully', function () {
// The current implementation doesn't handle null/undefined, so we test this behavior
// Note: Bun uses different error messages than Node.js
assert.throws(() => {
updateCheckerModule.compareVersionsWithPrerelease(null, '1.0.0');
}, /null is not an object|Cannot read properties of null/);
assert.throws(() => {
updateCheckerModule.compareVersionsWithPrerelease('1.0.0', null);
}, /null is not an object|Cannot read properties of null/);
assert.throws(() => {
updateCheckerModule.compareVersionsWithPrerelease(undefined, '1.0.0');
}, /undefined is not an object|Cannot read properties of undefined/);
assert.throws(() => {
updateCheckerModule.compareVersionsWithPrerelease('1.0.0', undefined);
}, /undefined is not an object|Cannot read properties of undefined/);
});
it('should handle version strings with extra whitespace', function () {
// The current implementation doesn't trim whitespace, so these will be treated as invalid versions
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease(' 1.0.0 ', '1.0.0'), -1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('1.0.0\n', '1.0.0'), -1);
assert.strictEqual(updateCheckerModule.compareVersionsWithPrerelease('\t1.0.0\t', '1.0.0'), -1);
});
it('should handle malformed version components', function () {
const malformedVersions = [
'1..3',
'1.2.',
'.2.3',
'1..3.4',
'a.b.c',
'1.2.3.4',
'1.2.3-dev',
'1.2.3-dev.',
'1.2.3-.1'
];
malformedVersions.forEach(version => {
assert.doesNotThrow(() => {
const result = updateCheckerModule.compareVersionsWithPrerelease(version, '1.0.0');
assert(typeof result === 'number', `Should return number for malformed version: ${version}`);
});
});
});
it('should handle very large version numbers', function () {
assert.strictEqual(
updateCheckerModule.compareVersionsWithPrerelease('999.999.999', '1.0.0'),
1
);
assert.strictEqual(
updateCheckerModule.compareVersionsWithPrerelease('1.0.0', '999.999.999'),
-1
);
});
it('should handle prerelease with large numbers', function () {
assert.strictEqual(
updateCheckerModule.compareVersionsWithPrerelease('1.0.0-dev.999', '1.0.0-dev.1'),
1
);
assert.strictEqual(
updateCheckerModule.compareVersionsWithPrerelease('1.0.0-dev.1', '1.0.0-dev.999'),
-1
);
});
it('should handle different prerelease identifiers case sensitivity', function () {
// Test case sensitivity - should be case insensitive
assert.strictEqual(
updateCheckerModule.compareVersionsWithPrerelease('1.0.0-DEV.1', '1.0.0-dev.1'),
0
);
assert.strictEqual(
updateCheckerModule.compareVersionsWithPrerelease('1.0.0-Alpha.1', '1.0.0-alpha.1'),
0
);
assert.strictEqual(
updateCheckerModule.compareVersionsWithPrerelease('1.0.0-Beta.1', '1.0.0-beta.1'),
0
);
});
});
describe('Integration tests with version comparison', function () {
it('should handle complex version comparison scenarios', function () {
const scenarios = [
// [version1, version2, expected_result, description]
['1.0.0', '1.0.0-alpha.1', 1, 'release > alpha'],
['1.0.0-alpha.1', '1.0.0-beta.1', 0, 'alpha = beta (same number, different identifier)'],
['1.0.0-beta.1', '1.0.0-rc.1', 0, 'beta = rc (same number, different identifier)'],
['1.0.0-rc.1', '1.0.0', -1, 'rc < release'],
['1.0.0-alpha.1', '1.0.0-alpha.2', -1, 'alpha.1 < alpha.2'],
['1.0.0-alpha.2', '1.0.0-alpha.1', 1, 'alpha.2 > alpha.1'],
['2.0.0-alpha.1', '1.9.9', 1, 'new major prerelease > old release'],
['1.0.0', '2.0.0-alpha.1', -1, 'old release < new major prerelease'],
];
scenarios.forEach(([v1, v2, expected, description]) => {
const result = updateCheckerModule.compareVersionsWithPrerelease(v1, v2);
assert.strictEqual(result, expected,
`Failed scenario: ${description} (${v1} vs ${v2})`);
});
});
});
});