kwgit
Version:
A command-line utility to help with cleaning up git branches. This tool provides a safe and efficient way to manage your git branches, helping you keep your repository clean and organized.
219 lines (167 loc) • 6.49 kB
JavaScript
import { cleanCommand } from './clean.js';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import * as gitService from '../services/gitService.js';
import * as prompts from '../utils/prompts.js';
describe('cleanCommand', () => {
beforeEach(() => {
vi.resetAllMocks();
});
it('should match branches by regex pattern', async () => {
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['feature/one', 'bugfix/two', 'hotfix/three']);
const deleteSpy = vi.spyOn(gitService, 'deleteBranch');
vi.spyOn(prompts, 'confirmBatchDeletion').mockResolvedValue(false); // don't delete
await cleanCommand.handler({
pattern: 'feature/.*',
dryRun: false,
merged: true,
force: false,
remote: false,
base: 'main',
});
expect(deleteSpy).not.toHaveBeenCalled();
});
it('should not delete anything in dry-run mode', async () => {
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['hotfix/safe']);
const deleteSpy = vi.spyOn(gitService, 'deleteBranch');
await cleanCommand.handler({
pattern: 'hotfix/.*',
dryRun: true,
merged: true,
force: true,
remote: false,
base: 'main',
});
expect(deleteSpy).not.toHaveBeenCalled();
});
it('should delete matching branches when confirmed', async () => {
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['feature/delete-me']);
const deleteSpy = vi.spyOn(gitService, 'deleteBranch').mockResolvedValue();
vi.spyOn(prompts, 'confirmBatchDeletion').mockResolvedValue(true);
await cleanCommand.handler({
pattern: 'feature/.*',
dryRun: false,
merged: true,
force: false,
remote: false,
base: 'main',
});
expect(deleteSpy).toHaveBeenCalledWith('feature/delete-me', false);
});
it('should delete remote branches if specified and they exist', async () => {
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['release/x']);
vi.spyOn(gitService, 'deleteBranch').mockResolvedValue();
vi.spyOn(gitService, 'remoteBranchExists').mockResolvedValue(true);
const remoteDeleteSpy = vi.spyOn(gitService, 'deleteRemoteBranch').mockResolvedValue();
vi.spyOn(prompts, 'confirmBatchDeletion').mockResolvedValue(true);
await cleanCommand.handler({
pattern: 'release/.*',
dryRun: false,
merged: true,
force: false,
remote: true,
base: 'main',
});
expect(remoteDeleteSpy).toHaveBeenCalledWith('release/x');
});
it('should skip remote deletion if remote branch does not exist', async () => {
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['release/ghost']);
vi.spyOn(gitService, 'deleteBranch').mockResolvedValue();
vi.spyOn(gitService, 'remoteBranchExists').mockResolvedValue(false);
const remoteDeleteSpy = vi.spyOn(gitService, 'deleteRemoteBranch').mockResolvedValue();
vi.spyOn(prompts, 'confirmBatchDeletion').mockResolvedValue(true);
await cleanCommand.handler({
pattern: 'release/.*',
dryRun: false,
merged: true,
force: false,
remote: true,
base: 'main',
});
expect(remoteDeleteSpy).not.toHaveBeenCalled();
});
it('should match multiple branches using regex', async () => {
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['feature/a', 'feature/b', 'bugfix/c']);
const deleteSpy = vi.spyOn(gitService, 'deleteBranch').mockResolvedValue();
vi.spyOn(prompts, 'confirmBatchDeletion').mockResolvedValue(true);
await cleanCommand.handler({
pattern: 'feature/.*',
dryRun: false,
merged: true,
force: false,
remote: false,
base: 'main',
});
expect(deleteSpy).toHaveBeenCalledTimes(2);
expect(deleteSpy).toHaveBeenCalledWith('feature/a', false);
expect(deleteSpy).toHaveBeenCalledWith('feature/b', false);
});
it('should not crash on invalid regex pattern', async () => {
const spy = vi.spyOn(console, 'error').mockImplementation(() => { });
await expect(cleanCommand.handler({
pattern: '[invalid-regex',
dryRun: true,
merged: true,
force: false,
remote: false,
base: 'main',
})).rejects.toThrow();
spy.mockRestore();
});
it('should skip deletion if no branches match', async () => {
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['feature/a']);
const deleteSpy = vi.spyOn(gitService, 'deleteBranch');
await cleanCommand.handler({
pattern: 'hotfix/.*',
dryRun: false,
merged: true,
force: true,
remote: false,
base: 'main',
});
expect(deleteSpy).not.toHaveBeenCalled();
});
it('should not delete if user declines prompt', async () => {
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['feature/nope']);
const deleteSpy = vi.spyOn(gitService, 'deleteBranch');
vi.spyOn(prompts, 'confirmBatchDeletion').mockResolvedValue(false);
await cleanCommand.handler({
pattern: 'feature/.*',
dryRun: false,
merged: true,
force: false,
remote: false,
base: 'main',
});
expect(deleteSpy).not.toHaveBeenCalled();
});
it('should use getLocalBranches when merged is false', async () => {
vi.spyOn(gitService, 'getLocalBranches').mockResolvedValue(['local-only']);
const deleteSpy = vi.spyOn(gitService, 'deleteBranch').mockResolvedValue();
vi.spyOn(prompts, 'confirmBatchDeletion').mockResolvedValue(true);
await cleanCommand.handler({
pattern: 'local-.*',
dryRun: false,
merged: false,
force: false,
remote: false,
base: 'main',
});
expect(deleteSpy).toHaveBeenCalledWith('local-only', false);
});
it('should skip protected branches and only delete unprotected ones', async () => {
process.env.KWGIT_PROTECTED_BRANCHES = 'main,develop';
vi.spyOn(gitService, 'getMergedBranches').mockResolvedValue(['main', 'develop', 'feature/clean-me']);
const deleteSpy = vi.spyOn(gitService, 'deleteBranch').mockResolvedValue();
vi.spyOn(prompts, 'confirmBatchDeletion').mockResolvedValue(true);
await cleanCommand.handler({
pattern: '.*',
dryRun: false,
merged: true,
force: false,
remote: false,
base: 'main',
});
expect(deleteSpy).toHaveBeenCalledTimes(1);
expect(deleteSpy).toHaveBeenCalledWith('feature/clean-me', false);
});
});