Skip to content

Commit 7998e03

Browse files
built-by-asclaude
andauthored
Sort branch list to prioritize main/master branches (#14)
* Sort branch list to prioritize main/master branches Modified the get-branches handler to sort the returned branch list so that 'main' and 'master' branches appear first, making them easier to select when creating new sessions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Add unit tests and extract branch logic to testable module - Extracted branch fetching and sorting logic into branch-utils.ts - Set up Jest testing framework with TypeScript support - Added 13 comprehensive unit tests with mocked simple-git - Simplified IPC handler to use extracted getBranches function - Sorting ensures main/master appear first without enforcing order between them 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Add GitHub Actions workflow for automated testing - Created test.yml workflow that runs on PRs and pushes to main - Runs npm test and npm run build to verify changes - Set up branch protection to require test job to pass before merging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Rename branch-utils to git-utils for broader scope - Renamed branch-utils.ts to git-utils.ts - Renamed branch-utils.test.ts to git-utils.test.ts - Updated imports in main.ts - Allows for adding other git-related functionality in the future 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent 158b271 commit 7998e03

File tree

7 files changed

+6806
-3091
lines changed

7 files changed

+6806
-3091
lines changed

.github/workflows/test.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Test
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Check out Git repository
17+
uses: actions/checkout@v4
18+
19+
- name: Install Node.js
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: 20
23+
24+
- name: Install dependencies
25+
run: npm install
26+
27+
- name: Run tests
28+
run: npm test
29+
30+
- name: Build
31+
run: npm run build

git-utils.test.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import {getBranches, sortBranchesWithMainFirst} from './git-utils';
2+
import {simpleGit} from 'simple-git';
3+
4+
// Mock simple-git
5+
jest.mock('simple-git');
6+
7+
describe('sortBranchesWithMainFirst', () => {
8+
it('should place main branch first', () => {
9+
const branches = ['feature/test', 'develop', 'main', 'bugfix/issue'];
10+
const sorted = sortBranchesWithMainFirst(branches);
11+
expect(sorted[0]).toBe('main');
12+
});
13+
14+
it('should place master branch first', () => {
15+
const branches = ['feature/test', 'develop', 'master', 'bugfix/issue'];
16+
const sorted = sortBranchesWithMainFirst(branches);
17+
expect(sorted[0]).toBe('master');
18+
});
19+
20+
it('should place both main and master at the top', () => {
21+
const branches = ['feature/test', 'master', 'main', 'develop'];
22+
const sorted = sortBranchesWithMainFirst(branches);
23+
expect(['main', 'master']).toContain(sorted[0]);
24+
expect(['main', 'master']).toContain(sorted[1]);
25+
});
26+
27+
it('should handle branches with main as substring', () => {
28+
const branches = ['feature/main-feature', 'main', 'main-branch'];
29+
const sorted = sortBranchesWithMainFirst(branches);
30+
expect(sorted[0]).toBe('main');
31+
expect(sorted).toContain('feature/main-feature');
32+
expect(sorted).toContain('main-branch');
33+
});
34+
35+
it('should preserve order of non-main/master branches', () => {
36+
const branches = ['feature/a', 'feature/b', 'feature/c'];
37+
const sorted = sortBranchesWithMainFirst([...branches]);
38+
expect(sorted).toEqual(branches);
39+
});
40+
41+
it('should handle empty array', () => {
42+
const sorted = sortBranchesWithMainFirst([]);
43+
expect(sorted).toEqual([]);
44+
});
45+
46+
it('should handle array with only main', () => {
47+
const sorted = sortBranchesWithMainFirst(['main']);
48+
expect(sorted).toEqual(['main']);
49+
});
50+
51+
it('should handle array with only master', () => {
52+
const sorted = sortBranchesWithMainFirst(['master']);
53+
expect(sorted).toEqual(['master']);
54+
});
55+
});
56+
57+
describe('getBranches', () => {
58+
const mockGit = {
59+
branch: jest.fn(),
60+
};
61+
62+
beforeEach(() => {
63+
jest.clearAllMocks();
64+
(simpleGit as jest.Mock).mockReturnValue(mockGit);
65+
});
66+
67+
it('should fetch and sort branches with main first', async () => {
68+
mockGit.branch.mockResolvedValue({
69+
all: ['feature/test', 'develop', 'main', 'bugfix/issue'],
70+
branches: {},
71+
current: 'main',
72+
detached: false,
73+
});
74+
75+
const branches = await getBranches('/test/path');
76+
77+
expect(simpleGit).toHaveBeenCalledWith('/test/path');
78+
expect(mockGit.branch).toHaveBeenCalled();
79+
expect(branches[0]).toBe('main');
80+
expect(branches).toHaveLength(4);
81+
});
82+
83+
it('should fetch and sort branches with master first', async () => {
84+
mockGit.branch.mockResolvedValue({
85+
all: ['feature/test', 'master', 'develop'],
86+
branches: {},
87+
current: 'master',
88+
detached: false,
89+
});
90+
91+
const branches = await getBranches('/test/path');
92+
93+
expect(branches[0]).toBe('master');
94+
expect(branches).toHaveLength(3);
95+
});
96+
97+
it('should handle errors and return empty array', async () => {
98+
mockGit.branch.mockRejectedValue(new Error('Git error'));
99+
100+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
101+
const branches = await getBranches('/test/path');
102+
103+
expect(branches).toEqual([]);
104+
expect(consoleErrorSpy).toHaveBeenCalledWith(
105+
'Error getting branches:',
106+
expect.any(Error)
107+
);
108+
109+
consoleErrorSpy.mockRestore();
110+
});
111+
112+
it('should handle empty branch list', async () => {
113+
mockGit.branch.mockResolvedValue({
114+
all: [],
115+
branches: {},
116+
current: '',
117+
detached: false,
118+
});
119+
120+
const branches = await getBranches('/test/path');
121+
122+
expect(branches).toEqual([]);
123+
});
124+
125+
it('should sort both main and master to the top', async () => {
126+
mockGit.branch.mockResolvedValue({
127+
all: ['feature/a', 'master', 'feature/b', 'main', 'develop'],
128+
branches: {},
129+
current: 'main',
130+
detached: false,
131+
});
132+
133+
const branches = await getBranches('/test/path');
134+
135+
expect(['main', 'master']).toContain(branches[0]);
136+
expect(['main', 'master']).toContain(branches[1]);
137+
});
138+
});

git-utils.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {simpleGit, SimpleGit} from "simple-git";
2+
3+
/**
4+
* Sort branches with main/master appearing first
5+
*/
6+
export function sortBranchesWithMainFirst(branches: string[]): string[] {
7+
return branches.sort((a, b) => {
8+
const aIsMainOrMaster = a === 'main' || a === 'master';
9+
const bIsMainOrMaster = b === 'main' || b === 'master';
10+
11+
if (aIsMainOrMaster && !bIsMainOrMaster) return -1;
12+
if (!aIsMainOrMaster && bIsMainOrMaster) return 1;
13+
14+
return 0;
15+
});
16+
}
17+
18+
/**
19+
* Get git branches from a directory, sorted with main/master first
20+
*/
21+
export async function getBranches(dirPath: string): Promise<string[]> {
22+
try {
23+
const git = simpleGit(dirPath);
24+
const branchSummary = await git.branch();
25+
const branches = branchSummary.all;
26+
return sortBranchesWithMainFirst(branches);
27+
} catch (error) {
28+
console.error("Error getting branches:", error);
29+
return [];
30+
}
31+
}

jest.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
testMatch: ['**/*.test.ts'],
5+
collectCoverageFrom: [
6+
'**/*.ts',
7+
'!**/*.test.ts',
8+
'!dist/**',
9+
'!node_modules/**',
10+
],
11+
};

main.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {promisify} from "util";
1010
import {v4 as uuidv4} from "uuid";
1111
import {PersistedSession, SessionConfig} from "./types";
1212
import {isTerminalReady} from "./terminal-utils";
13+
import {getBranches} from "./git-utils";
1314

1415
const execAsync = promisify(exec);
1516

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

390391
// Get git branches from directory
391392
ipcMain.handle("get-branches", async (_event, dirPath: string) => {
392-
try {
393-
const git = simpleGit(dirPath);
394-
const branchSummary = await git.branch();
395-
return branchSummary.all;
396-
} catch (error) {
397-
console.error("Error getting branches:", error);
398-
return [];
399-
}
393+
return getBranches(dirPath);
400394
});
401395

402396
// Get last used settings

0 commit comments

Comments
 (0)