semantic-release-pr-fix
Version:
Wrapper for @semantic-release/commit-analyzer that handles Azure DevOps PR merge commits
317 lines (258 loc) • 11 kB
JavaScript
// Mock the @semantic-release/commit-analyzer module
const mockAnalyzeCommits = jest.fn().mockImplementation((pluginConfig, context) => {
// Just return the type based on the first commit message for testing
if (context.commits && context.commits.length > 0) {
const msg = context.commits[0].message.toLowerCase();
if (msg.startsWith('feat:')) return 'minor';
if (msg.startsWith('fix:')) return 'patch';
if (msg.startsWith('breaking:')) return 'major';
return null;
}
return null;
});
// Mock the @semantic-release/release-notes-generator module
const mockGenerateNotes = jest.fn().mockImplementation((pluginConfig, context) => {
if (context.commits && context.commits.length > 0) {
const msg = context.commits[0].message.toLowerCase();
if (msg.startsWith('feat:')) return 'Feature: ' + context.commits[0].message;
if (msg.startsWith('fix:')) return 'Fix: ' + context.commits[0].message;
if (msg.startsWith('breaking:')) return 'BREAKING CHANGE: ' + context.commits[0].message;
return 'Other changes: ' + context.commits[0].message;
}
return 'No changes';
});
jest.mock('@semantic-release/commit-analyzer', () => ({
analyzeCommits: mockAnalyzeCommits
}));
jest.mock('@semantic-release/release-notes-generator', () => ({
generateNotes: mockGenerateNotes
}));
const { analyzeCommits, generateNotes } = require('../src/index');
describe('semantic-release-pr-fix', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should process Azure DevOps PR commit message by removing the prefix', async () => {
const context = {
commits: [
{
message: 'Merged PR 1234: feat: add new feature',
subject: 'Merged PR 1234: feat: add new feature'
}
]
};
const result = await analyzeCommits({}, context);
// Check that the prefix was removed
expect(context.commits[0].message).toBe('feat: add new feature');
expect(context.commits[0].subject).toBe('feat: add new feature');
// Check that commit-analyzer was called with the transformed commits and empty config
expect(mockAnalyzeCommits).toHaveBeenCalledWith({}, context);
expect(result).toBe('minor');
});
test('should pass commitAnalyzerConfig to the analyzer', async () => {
const context = {
commits: [
{
message: 'Merged PR 1234: feat: add new feature',
subject: 'Merged PR 1234: feat: add new feature'
}
]
};
const pluginConfig = {
commitAnalyzerConfig: {
preset: 'angular',
releaseRules: [
{type: 'docs', scope: 'README', release: 'patch'}
]
}
};
const result = await analyzeCommits(pluginConfig, context);
// Check that the prefix was removed
expect(context.commits[0].message).toBe('feat: add new feature');
expect(context.commits[0].subject).toBe('feat: add new feature');
// Check that commit-analyzer was called with the specific config
expect(mockAnalyzeCommits).toHaveBeenCalledWith(pluginConfig.commitAnalyzerConfig, context);
expect(result).toBe('minor');
});
test('should handle commits without the Azure DevOps PR prefix', async () => {
const context = {
commits: [
{
message: 'fix: resolve bug in login function',
subject: 'fix: resolve bug in login function'
}
]
};
const originalMessage = context.commits[0].message;
const originalSubject = context.commits[0].subject;
const result = await analyzeCommits({}, context);
// Message should be unchanged
expect(context.commits[0].message).toBe(originalMessage);
expect(context.commits[0].subject).toBe(originalSubject);
// Check that commit-analyzer was called
expect(mockAnalyzeCommits).toHaveBeenCalledWith({}, context);
// Our mock analyzer should return 'patch' for a fix
expect(result).toBe('patch');
});
test('should handle multiple commits with and without prefixes', async () => {
const context = {
commits: [
{
message: 'Merged PR 1234: feat: add new feature',
subject: 'Merged PR 1234: feat: add new feature'
},
{
message: 'fix: resolve bug in login function',
subject: 'fix: resolve bug in login function'
},
{
message: 'Merged PR 56789123: breaking: change API structure',
subject: 'Merged PR 56789123: breaking: change API structure'
}
]
};
await analyzeCommits({}, context);
// Check that prefixes were removed only where they existed
expect(context.commits[0].message).toBe('feat: add new feature');
expect(context.commits[1].message).toBe('fix: resolve bug in login function');
expect(context.commits[2].message).toBe('breaking: change API structure');
expect(mockAnalyzeCommits).toHaveBeenCalledWith({}, context);
});
test('should handle empty or undefined commits', async () => {
// Test with undefined commits
const emptyContext = {};
await analyzeCommits({}, emptyContext);
expect(mockAnalyzeCommits).toHaveBeenCalledWith({}, emptyContext);
// Test with empty commits array
const contextWithEmptyCommits = { commits: [] };
await analyzeCommits({}, contextWithEmptyCommits);
expect(mockAnalyzeCommits).toHaveBeenCalledWith({}, contextWithEmptyCommits);
});
test('should handle complex Azure DevOps PR messages', async () => {
const context = {
commits: [
{
message: 'Merged PR 9999: fix: bugfix with detailed explanation of what was fixed',
subject: 'Merged PR 9999: fix: bugfix with detailed explanation of what was fixed'
},
{
message: 'Merged PR 8888: This is a normal commit message without semantic prefix',
subject: 'Merged PR 8888: This is a normal commit message without semantic prefix'
}
]
};
await analyzeCommits({}, context);
expect(context.commits[0].message).toBe('fix: bugfix with detailed explanation of what was fixed');
expect(context.commits[1].message).toBe('This is a normal commit message without semantic prefix');
expect(mockAnalyzeCommits).toHaveBeenCalledWith({}, context);
});
describe('generateNotes', () => {
test('should process Azure DevOps PR commit message for release notes', async () => {
const context = {
commits: [
{
message: 'Merged PR 1234: feat: add new feature',
subject: 'Merged PR 1234: feat: add new feature'
}
]
};
const notes = await generateNotes({}, context);
// Check that the prefix was removed
expect(context.commits[0].message).toBe('feat: add new feature');
expect(context.commits[0].subject).toBe('feat: add new feature');
// Check that notes generator was called with the transformed commits and empty config
expect(mockGenerateNotes).toHaveBeenCalledWith({}, context);
// Our mock generator should return notes based on the first commit
expect(notes).toBe('Feature: feat: add new feature');
});
test('should pass notesGeneratorConfig to the notes generator', async () => {
const context = {
commits: [
{
message: 'Merged PR 1234: feat: add new feature',
subject: 'Merged PR 1234: feat: add new feature'
}
]
};
const pluginConfig = {
notesGeneratorConfig: {
preset: 'angular',
writerOpts: {
commitsSort: ['subject', 'scope']
}
}
};
const notes = await generateNotes(pluginConfig, context);
// Check that the prefix was removed
expect(context.commits[0].message).toBe('feat: add new feature');
expect(context.commits[0].subject).toBe('feat: add new feature');
// Check that notes generator was called with the specific config
expect(mockGenerateNotes).toHaveBeenCalledWith(pluginConfig.notesGeneratorConfig, context);
// Our mock generator should return notes based on the first commit
expect(notes).toBe('Feature: feat: add new feature');
});
test('should skip notes generation if notesGeneratorConfig is false', async () => {
const context = {
commits: [
{
message: 'Merged PR 1234: feat: add new feature',
subject: 'Merged PR 1234: feat: add new feature'
}
]
};
const pluginConfig = {
notesGeneratorConfig: false
};
const notes = await generateNotes(pluginConfig, context);
// Notes should be undefined
expect(notes).toBeUndefined();
// The notes generator should not be called
expect(mockGenerateNotes).not.toHaveBeenCalled();
});
test('should handle commits without the Azure DevOps PR prefix when generating notes', async () => {
const context = {
commits: [
{
message: 'fix: resolve bug in login function',
subject: 'fix: resolve bug in login function'
}
]
};
const originalMessage = context.commits[0].message;
const originalSubject = context.commits[0].subject;
const notes = await generateNotes({}, context);
// Message should be unchanged
expect(context.commits[0].message).toBe(originalMessage);
expect(context.commits[0].subject).toBe(originalSubject);
// Check that notes generator was called
expect(mockGenerateNotes).toHaveBeenCalledWith({}, context);
// Our mock generator should return notes based on the commit
expect(notes).toBe('Fix: fix: resolve bug in login function');
});
test('should handle multiple commits with and without prefixes for release notes', async () => {
const context = {
commits: [
{
message: 'Merged PR 1234: feat: add new feature',
subject: 'Merged PR 1234: feat: add new feature'
},
{
message: 'fix: resolve bug in login function',
subject: 'fix: resolve bug in login function'
},
{
message: 'Merged PR 56789123: breaking: change API structure',
subject: 'Merged PR 56789123: breaking: change API structure'
}
]
};
const notes = await generateNotes({}, context);
// Check that prefixes were removed only where they existed
expect(context.commits[0].message).toBe('feat: add new feature');
expect(context.commits[1].message).toBe('fix: resolve bug in login function');
expect(context.commits[2].message).toBe('breaking: change API structure');
expect(mockGenerateNotes).toHaveBeenCalledWith({}, context);
expect(notes).toBe('Feature: feat: add new feature');
});
});
});