Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Test

on:
pull_request:
branches:
- main
push:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Check out Git repository
uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: npm install

- name: Run tests
run: npm test

- name: Build
run: npm run build
138 changes: 138 additions & 0 deletions git-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {getBranches, sortBranchesWithMainFirst} from './git-utils';
import {simpleGit} from 'simple-git';

// Mock simple-git
jest.mock('simple-git');

describe('sortBranchesWithMainFirst', () => {
it('should place main branch first', () => {
const branches = ['feature/test', 'develop', 'main', 'bugfix/issue'];
const sorted = sortBranchesWithMainFirst(branches);
expect(sorted[0]).toBe('main');
});

it('should place master branch first', () => {
const branches = ['feature/test', 'develop', 'master', 'bugfix/issue'];
const sorted = sortBranchesWithMainFirst(branches);
expect(sorted[0]).toBe('master');
});

it('should place both main and master at the top', () => {
const branches = ['feature/test', 'master', 'main', 'develop'];
const sorted = sortBranchesWithMainFirst(branches);
expect(['main', 'master']).toContain(sorted[0]);
expect(['main', 'master']).toContain(sorted[1]);
});

it('should handle branches with main as substring', () => {
const branches = ['feature/main-feature', 'main', 'main-branch'];
const sorted = sortBranchesWithMainFirst(branches);
expect(sorted[0]).toBe('main');
expect(sorted).toContain('feature/main-feature');
expect(sorted).toContain('main-branch');
});

it('should preserve order of non-main/master branches', () => {
const branches = ['feature/a', 'feature/b', 'feature/c'];
const sorted = sortBranchesWithMainFirst([...branches]);
expect(sorted).toEqual(branches);
});

it('should handle empty array', () => {
const sorted = sortBranchesWithMainFirst([]);
expect(sorted).toEqual([]);
});

it('should handle array with only main', () => {
const sorted = sortBranchesWithMainFirst(['main']);
expect(sorted).toEqual(['main']);
});

it('should handle array with only master', () => {
const sorted = sortBranchesWithMainFirst(['master']);
expect(sorted).toEqual(['master']);
});
});

describe('getBranches', () => {
const mockGit = {
branch: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
(simpleGit as jest.Mock).mockReturnValue(mockGit);
});

it('should fetch and sort branches with main first', async () => {
mockGit.branch.mockResolvedValue({
all: ['feature/test', 'develop', 'main', 'bugfix/issue'],
branches: {},
current: 'main',
detached: false,
});

const branches = await getBranches('/test/path');

expect(simpleGit).toHaveBeenCalledWith('/test/path');
expect(mockGit.branch).toHaveBeenCalled();
expect(branches[0]).toBe('main');
expect(branches).toHaveLength(4);
});

it('should fetch and sort branches with master first', async () => {
mockGit.branch.mockResolvedValue({
all: ['feature/test', 'master', 'develop'],
branches: {},
current: 'master',
detached: false,
});

const branches = await getBranches('/test/path');

expect(branches[0]).toBe('master');
expect(branches).toHaveLength(3);
});

it('should handle errors and return empty array', async () => {
mockGit.branch.mockRejectedValue(new Error('Git error'));

const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
const branches = await getBranches('/test/path');

expect(branches).toEqual([]);
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Error getting branches:',
expect.any(Error)
);

consoleErrorSpy.mockRestore();
});

it('should handle empty branch list', async () => {
mockGit.branch.mockResolvedValue({
all: [],
branches: {},
current: '',
detached: false,
});

const branches = await getBranches('/test/path');

expect(branches).toEqual([]);
});

it('should sort both main and master to the top', async () => {
mockGit.branch.mockResolvedValue({
all: ['feature/a', 'master', 'feature/b', 'main', 'develop'],
branches: {},
current: 'main',
detached: false,
});

const branches = await getBranches('/test/path');

expect(['main', 'master']).toContain(branches[0]);
expect(['main', 'master']).toContain(branches[1]);
});
});
31 changes: 31 additions & 0 deletions git-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {simpleGit, SimpleGit} from "simple-git";

/**
* Sort branches with main/master appearing first
*/
export function sortBranchesWithMainFirst(branches: string[]): string[] {
return branches.sort((a, b) => {
const aIsMainOrMaster = a === 'main' || a === 'master';
const bIsMainOrMaster = b === 'main' || b === 'master';

if (aIsMainOrMaster && !bIsMainOrMaster) return -1;
if (!aIsMainOrMaster && bIsMainOrMaster) return 1;

return 0;
});
}

/**
* Get git branches from a directory, sorted with main/master first
*/
export async function getBranches(dirPath: string): Promise<string[]> {
try {
const git = simpleGit(dirPath);
const branchSummary = await git.branch();
const branches = branchSummary.all;
return sortBranchesWithMainFirst(branches);
} catch (error) {
console.error("Error getting branches:", error);
return [];
}
}
11 changes: 11 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
collectCoverageFrom: [
'**/*.ts',
'!**/*.test.ts',
'!dist/**',
'!node_modules/**',
],
};
10 changes: 2 additions & 8 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {promisify} from "util";
import {v4 as uuidv4} from "uuid";
import {PersistedSession, SessionConfig} from "./types";
import {isTerminalReady} from "./terminal-utils";
import {getBranches} from "./git-utils";

const execAsync = promisify(exec);

Expand Down Expand Up @@ -389,14 +390,7 @@ ipcMain.handle("select-directory", async () => {

// Get git branches from directory
ipcMain.handle("get-branches", async (_event, dirPath: string) => {
try {
const git = simpleGit(dirPath);
const branchSummary = await git.branch();
return branchSummary.all;
} catch (error) {
console.error("Error getting branches:", error);
return [];
}
return getBranches(dirPath);
});

// Get last used settings
Expand Down
Loading