Skip to content

Commit

Permalink
feat(tests): add test coverage for semver (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
virgofx authored Oct 29, 2024
1 parent 845180e commit 367702d
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 17 deletions.
40 changes: 28 additions & 12 deletions __tests__/__mocks__/config.mock.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { merge } from 'ts-deepmerge';
import { vi } from 'vitest';
import type { Config } from '../../src/config';

type InputMap = {
[key: string]: string;
};

const defaultInputs: InputMap = {
'major-keywords': 'BREAKING CHANGE,!',
'major-keywords': 'MAJOR CHANGE,BREAKING CHANGE,!',
'minor-keywords': 'feat,feature',
'patch-keywords': 'fix,chore',
'default-first-tag': 'v0.1.0',
Expand All @@ -22,10 +21,10 @@ const defaultInputs: InputMap = {
};

const defaultConfig: Config = {
majorKeywords: ['BREAKING CHANGE', '!'],
majorKeywords: ['BREAKING CHANGE', '!', 'MAJOR CHANGE'],
minorKeywords: ['feat', 'feature'],
patchKeywords: ['fix', 'chore'],
defaultFirstTag: 'v0.1.0',
defaultFirstTag: 'v1.0.0',
terraformDocsVersion: 'v0.19.0',
deleteLegacyTags: false,
disableWiki: false,
Expand All @@ -36,16 +35,33 @@ const defaultConfig: Config = {
githubToken: 'ghp_test_token_2c6912E7710c838347Ae178B4',
};

// Create a mock factory function
export const createConfigMock = (overrides: Partial<Config> = {}) => ({
...defaultConfig,
...overrides,
});

// Create a mock inputs factory function
export function createInputsMock(inputs: InputMap = {}): InputMap {
return merge(defaultInputs, inputs);
}

// Create the mock handler
export const configMock = vi.fn(() => defaultConfig);
// Create a mocked config object with a set method to deep merge additional ovverides.
export const configMock: Config & {
reset: () => void;
set: (overrides?: Partial<Config>) => void;
} = {
...defaultConfig,

reset: () => {
for (const key of Object.keys(defaultConfig)) {
if (key !== 'reset' && key !== 'set') {
configMock[key] = defaultConfig[key];
}
}
},

// Method to update specific values
set: (overrides: Partial<Config> = {}) => {
const updated = merge(configMock, overrides);
for (const key of Object.keys(updated)) {
if (key !== 'reset' && key !== 'set') {
configMock[key] = updated[key];
}
}
},
};
4 changes: 2 additions & 2 deletions __tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ describe('config', () => {
it('should initialize with valid inputs and log configuration', async () => {
const config = getConfig();

expect(config.majorKeywords).toEqual(['BREAKING CHANGE', '!']);
expect(config.majorKeywords).toEqual(['MAJOR CHANGE', 'BREAKING CHANGE', '!']);
expect(config.minorKeywords).toEqual(['feat', 'feature']);
expect(config.patchKeywords).toEqual(['fix', 'chore']);
expect(config.defaultFirstTag).toBe('v0.1.0');
Expand All @@ -214,7 +214,7 @@ describe('config', () => {
expect(mockEndGroup).toHaveBeenCalledTimes(1);
expect(mockInfo).toHaveBeenCalledTimes(10);
expect(mockInfo.mock.calls).toEqual([
['Major Keywords: BREAKING CHANGE, !'],
['Major Keywords: MAJOR CHANGE, BREAKING CHANGE, !'],
['Minor Keywords: feat, feature'],
['Patch Keywords: fix, chore'],
['Default First Tag: v0.1.0'],
Expand Down
101 changes: 101 additions & 0 deletions __tests__/semver.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { describe, expect, it } from 'vitest';
import { determineReleaseType, getNextTagVersion } from '../src/semver';
import { configMock } from './__mocks__/config.mock';

describe('semver', () => {
describe('determineReleaseType', () => {
it('should return major when commit message contains major keyword', () => {
configMock.set({
majorKeywords: ['major change', 'breaking change'],
});
const message = 'BREAKING CHANGE: completely restructured API';
expect(determineReleaseType(message)).toBe('major');
});

it('should return minor when commit message contains minor keyword', () => {
const message = 'feat: added new feature';
expect(determineReleaseType(message)).toBe('minor');
});

it('should return patch by default for regular commit messages', () => {
const message = 'fix: fixed a small bug';
expect(determineReleaseType(message)).toBe('patch');
});

it('should be case insensitive when checking keywords', () => {
configMock.set({
majorKeywords: ['BReaKING CHANGE', '!', 'major CHANGE'],
});
const message = 'bReAkInG cHaNgE: major update';
expect(determineReleaseType(message)).toBe('major');
});

it('should handle empty commit messages', () => {
expect(determineReleaseType('')).toBe('patch');
});

it('should consider previous release type when determining new release type', () => {
// If previous release was major, next should be major regardless of message
expect(determineReleaseType('fix: small update', 'major')).toBe('major');

// If previous release was minor, next should be at least minor
expect(determineReleaseType('fix: small update', 'minor')).toBe('minor');

// If previous was patch, message determines new type
expect(determineReleaseType('fix: small update', 'patch')).toBe('patch');
});

it('should handle null previous release type', () => {
expect(determineReleaseType('fix: small update', null)).toBe('patch');
});

it('should trim whitespace from commit messages', () => {
const message = ' BREAKING CHANGE: major update ';
expect(determineReleaseType(message)).toBe('major');
});
});

describe('getNextTagVersion', () => {
it('should return default first tag when latest tag is null', () => {
const defaultTag = 'v3.5.1';
configMock.set({
defaultFirstTag: defaultTag,
});
expect(getNextTagVersion(null, 'patch')).toBe(defaultTag);
});

it('should increment major version correctly', () => {
expect(getNextTagVersion('v1.2.3', 'major')).toBe('v2.0.0');
});

it('should increment minor version correctly', () => {
expect(getNextTagVersion('v1.2.3', 'minor')).toBe('v1.3.0');
});

it('should increment patch version correctly', () => {
expect(getNextTagVersion('v1.2.3', 'patch')).toBe('v1.2.4');
});

it('should handle version tags without v prefix', () => {
expect(getNextTagVersion('1.2.3', 'major')).toBe('v2.0.0');
expect(getNextTagVersion('1.2.3', 'minor')).toBe('v1.3.0');
expect(getNextTagVersion('1.2.3', 'patch')).toBe('v1.2.4');
});

it('should reset minor and patch versions when incrementing major', () => {
expect(getNextTagVersion('v1.2.3', 'major')).toBe('v2.0.0');
});

it('should reset patch version when incrementing minor', () => {
expect(getNextTagVersion('v1.2.3', 'minor')).toBe('v1.3.0');
});

it('should handle version numbers with single digits', () => {
expect(getNextTagVersion('v1.0.0', 'patch')).toBe('v1.0.1');
});

it('should handle version numbers with multiple digits', () => {
expect(getNextTagVersion('v10.20.30', 'patch')).toBe('v10.20.31');
});
});
});
3 changes: 3 additions & 0 deletions __tests__/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ afterEach(() => {

// Clear mocks before each test
vi.resetAllMocks();

// For now, we'll reset all config mocks to utilize default values
configMock.reset();
});
2 changes: 1 addition & 1 deletion assets/coverage-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/semver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export function determineReleaseType(message: string, previousReleaseType: Relea

// Determine release type from message
let currentReleaseType: ReleaseType = 'patch';
if (majorKeywords.some((keyword) => messageCleaned.includes(keyword))) {
if (majorKeywords.some((keyword) => messageCleaned.includes(keyword.toLowerCase()))) {
currentReleaseType = 'major';
} else if (minorKeywords.some((keyword) => messageCleaned.includes(keyword))) {
} else if (minorKeywords.some((keyword) => messageCleaned.includes(keyword.toLowerCase()))) {
currentReleaseType = 'minor';
}

Expand Down

0 comments on commit 367702d

Please sign in to comment.