Skip to content

Commit 0c0fd26

Browse files
authored
[AXON-1319, AXON-1254]: [Start Work Page] Custom Branch Template rule is not correctly applied (#1112)
* AXON-1319: properly usage of prefixes in non-Bitbucket repos * AXON-1319: moved functions to utils, separated tests
1 parent abadea7 commit 0c0fd26

File tree

4 files changed

+141
-69
lines changed

4 files changed

+141
-69
lines changed

src/react/atlascode/startwork/v3/hooks/useStartWorkFormState.test.tsx

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ jest.mock('../../startWorkController', () => ({
1717
jest.mock('../utils/branchUtils', () => ({
1818
getDefaultSourceBranch: jest.fn().mockReturnValue({ type: 0, name: 'main' }),
1919
generateBranchName: jest.fn().mockReturnValue('generated-branch-name'),
20+
getBranchTypeForRepo: jest.fn(),
2021
}));
2122

2223
const mockGenerateBranchName = require('../utils/branchUtils').generateBranchName;
24+
const mockGetBranchTypeForRepo = require('../utils/branchUtils').getBranchTypeForRepo;
2325

2426
describe('useStartWorkFormState', () => {
2527
const mockController = {
@@ -73,76 +75,70 @@ describe('useStartWorkFormState', () => {
7375
beforeEach(() => {
7476
jest.clearAllMocks();
7577
mockGenerateBranchName.mockReturnValue('generated-branch-name');
78+
79+
mockGetBranchTypeForRepo.mockImplementation((repo: RepoData, customPrefixes: string[]) => {
80+
if (repo.branchTypes?.length > 0) {
81+
return repo.branchTypes[0];
82+
} else if (customPrefixes.length > 0) {
83+
return { kind: customPrefixes[0], prefix: customPrefixes[0] + '/' };
84+
} else {
85+
return { kind: '', prefix: '' };
86+
}
87+
});
7688
});
7789

78-
describe('handleRepositoryChange', () => {
79-
it('should reset selectedBranchType to first branchType when switching to Bitbucket repo', () => {
90+
describe('hook behavior', () => {
91+
it('should initialize with correct branch type from utils', () => {
8092
const { result } = renderHook(() => useStartWorkFormState(mockState, mockController));
8193

82-
act(() => {
83-
result.current.formActions.onRepositoryChange(mockBitbucketRepo);
84-
});
85-
94+
// Hook should use the result from getBranchTypeForRepo
8695
expect(result.current.formState.selectedBranchType).toEqual({
8796
kind: 'Feature',
8897
prefix: 'feature/',
8998
});
9099
});
91100

92-
it('should reset selectedBranchType to empty when switching to non-Bitbucket repo without custom prefixes', () => {
101+
it('should update branch type when repository changes', () => {
93102
const { result } = renderHook(() => useStartWorkFormState(mockState, mockController));
94103

95104
act(() => {
96105
result.current.formActions.onRepositoryChange(mockGitHubRepo);
97106
});
98107

108+
// Hook should call utils and update state accordingly
99109
expect(result.current.formState.selectedBranchType).toEqual({
100110
kind: '',
101111
prefix: '',
102112
});
103113
});
114+
});
104115

105-
it('should reset selectedBranchType to first custom prefix when switching to non-Bitbucket repo with custom prefixes', () => {
106-
const stateWithCustomPrefixes = {
107-
...mockState,
108-
customPrefixes: ['hotfix', 'chore'],
109-
};
116+
describe('integration with utils', () => {
117+
it('should call getBranchTypeForRepo with correct parameters on initialization', () => {
118+
renderHook(() => useStartWorkFormState(mockState, mockController));
110119

111-
const { result } = renderHook(() => useStartWorkFormState(stateWithCustomPrefixes, mockController));
120+
expect(mockGetBranchTypeForRepo).toHaveBeenCalledWith(mockBitbucketRepo, mockState.customPrefixes);
121+
});
122+
123+
it('should call getBranchTypeForRepo when repository changes', () => {
124+
const { result } = renderHook(() => useStartWorkFormState(mockState, mockController));
112125

113126
act(() => {
114127
result.current.formActions.onRepositoryChange(mockGitHubRepo);
115128
});
116129

117-
expect(result.current.formState.selectedBranchType).toEqual({
118-
kind: 'hotfix',
119-
prefix: 'hotfix/',
120-
});
130+
expect(mockGetBranchTypeForRepo).toHaveBeenCalledWith(mockGitHubRepo, mockState.customPrefixes);
121131
});
122-
});
123-
124-
describe('branch name generation', () => {
125-
it('should generate branch name with prefix when selectedBranchType has prefix', () => {
126-
mockGenerateBranchName.mockReturnValue('feature/TEST-123-test-issue');
127132

133+
it('should call generateBranchName when repository or branch type changes', () => {
128134
const { result } = renderHook(() => useStartWorkFormState(mockState, mockController));
129135

130-
act(() => {
131-
result.current.formActions.onRepositoryChange(mockBitbucketRepo);
132-
});
133-
134136
expect(mockGenerateBranchName).toHaveBeenCalledWith(
135137
mockBitbucketRepo,
136138
{ kind: 'Feature', prefix: 'feature/' },
137139
mockState.issue,
138140
mockState.customTemplate,
139141
);
140-
});
141-
142-
it('should generate branch name without prefix when selectedBranchType has no prefix', () => {
143-
mockGenerateBranchName.mockReturnValue('TEST-123-test-issue');
144-
145-
const { result } = renderHook(() => useStartWorkFormState(mockState, mockController));
146142

147143
act(() => {
148144
result.current.formActions.onRepositoryChange(mockGitHubRepo);
@@ -152,7 +148,7 @@ describe('useStartWorkFormState', () => {
152148
mockGitHubRepo,
153149
{ kind: '', prefix: '' },
154150
mockState.issue,
155-
'{{issueKey}}-{{summary}}', // Template without prefix
151+
mockState.customTemplate,
156152
);
157153
});
158154
});

src/react/atlascode/startwork/v3/hooks/useStartWorkFormState.ts

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { RepoData } from '../../../../../lib/ipc/toUI/startWork';
66
import { Branch } from '../../../../../typings/git';
77
import { ErrorControllerContext } from '../../../common/errorController';
88
import { useStartWorkController } from '../../startWorkController';
9-
import { generateBranchName, getDefaultSourceBranch } from '../utils/branchUtils';
9+
import { generateBranchName, getBranchTypeForRepo, getDefaultSourceBranch } from '../utils/branchUtils';
1010

1111
export function useStartWorkFormState(
1212
state: ReturnType<typeof useStartWorkController>[0],
@@ -34,6 +34,10 @@ export function useStartWorkFormState(
3434
}>({});
3535
const [snackbarOpen, setSnackbarOpen] = useState(false);
3636
const [startWithRovoDev, setStartWithRovoDev] = useState(state.rovoDevPreference || false);
37+
const getBranchTypeForCurrentRepo = useCallback(
38+
(repo: RepoData) => getBranchTypeForRepo(repo, state.customPrefixes),
39+
[state.customPrefixes],
40+
);
3741

3842
useEffect(() => {
3943
controller.postMessage({
@@ -53,59 +57,36 @@ export function useStartWorkFormState(
5357
const defaultRepo = state.repoData[0];
5458
setSelectedRepository(defaultRepo);
5559
setSourceBranch(getDefaultSourceBranch(defaultRepo));
56-
if (defaultRepo.branchTypes?.length > 0) {
57-
setSelectedBranchType(defaultRepo.branchTypes[0]);
58-
}
60+
61+
// Set branch type based on repo's branch types or custom prefixes
62+
setSelectedBranchType(getBranchTypeForCurrentRepo(defaultRepo));
63+
5964
if (!upstream) {
6065
setUpstream(defaultRepo.workspaceRepo.mainSiteRemote.remote.name);
6166
}
6267
}
63-
}, [state.repoData, upstream]);
68+
}, [state.repoData, upstream, getBranchTypeForCurrentRepo]);
6469

6570
// useEffect: auto-generate branch name
6671
useEffect(() => {
6772
if (selectedRepository) {
68-
if (selectedBranchType.prefix) {
69-
// Generate branch name with prefix using the custom template
70-
setLocalBranch(
71-
generateBranchName(selectedRepository, selectedBranchType, state.issue, state.customTemplate),
72-
);
73-
} else {
74-
// No prefix available, generate branch name without prefix using a template without prefix
75-
const emptyBranchType = { kind: '', prefix: '' };
76-
const templateWithoutPrefix = '{{issueKey}}-{{summary}}';
77-
setLocalBranch(
78-
generateBranchName(selectedRepository, emptyBranchType, state.issue, templateWithoutPrefix),
79-
);
80-
}
73+
setLocalBranch(
74+
generateBranchName(selectedRepository, selectedBranchType, state.issue, state.customTemplate),
75+
);
8176
}
8277
}, [selectedRepository, selectedBranchType, state.issue, state.customTemplate]);
8378

8479
const handleRepositoryChange = useCallback(
8580
(repository: RepoData) => {
8681
setSelectedRepository(repository);
8782
setSourceBranch(getDefaultSourceBranch(repository));
88-
89-
if (repository.branchTypes?.length > 0) {
90-
setSelectedBranchType(repository.branchTypes[0]);
91-
} else {
92-
const convertedCustomPrefixes = state.customPrefixes.map((prefix) => {
93-
const normalizedCustomPrefix = prefix.endsWith('/') ? prefix : prefix + '/';
94-
return { prefix: normalizedCustomPrefix, kind: prefix };
95-
});
96-
97-
if (convertedCustomPrefixes.length > 0) {
98-
setSelectedBranchType(convertedCustomPrefixes[0]);
99-
} else {
100-
setSelectedBranchType({ kind: '', prefix: '' });
101-
}
102-
}
83+
setSelectedBranchType(getBranchTypeForCurrentRepo(repository));
10384

10485
if (!upstream) {
10586
setUpstream(repository.workspaceRepo.mainSiteRemote.remote.name);
10687
}
10788
},
108-
[upstream, state.customPrefixes],
89+
[upstream, getBranchTypeForCurrentRepo],
10990
);
11091

11192
const handleBranchTypeChange = useCallback((branchType: { kind: string; prefix: string }) => {

src/react/atlascode/startwork/v3/utils/branchUtils.test.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jest.mock('mustache', () => ({
77

88
const mockMustacheRender = require('mustache').default.render;
99

10-
import { generateBranchName, getAllBranches, getDefaultSourceBranch } from './branchUtils';
10+
import { generateBranchName, getAllBranches, getBranchTypeForRepo, getDefaultSourceBranch } from './branchUtils';
1111

1212
describe('branchUtils', () => {
1313
const mockRepoData = {
@@ -154,5 +154,81 @@ describe('branchUtils', () => {
154154
);
155155
expect(result).toBe('TEST-123-Test-issue-summary');
156156
});
157+
158+
it('should use custom template even when prefix is empty', () => {
159+
mockMustacheRender.mockReturnValue('test.user/TEST-123-Test-issue-summary');
160+
const emptyBranchType = {
161+
kind: '',
162+
prefix: '',
163+
};
164+
const customTemplateWithUsername = '{{username}}/{{issueKey}}-{{summary}}';
165+
const result = generateBranchName(mockRepo, emptyBranchType, mockIssue, customTemplateWithUsername);
166+
expect(mockMustacheRender).toHaveBeenCalledWith(
167+
customTemplateWithUsername,
168+
expect.objectContaining({
169+
username: 'test.user',
170+
prefix: '',
171+
issueKey: 'TEST-123',
172+
summary: 'test-issue-summary',
173+
}),
174+
);
175+
expect(result).toBe('test.user/TEST-123-Test-issue-summary');
176+
});
177+
});
178+
179+
describe('getBranchTypeForRepo', () => {
180+
const mockRepoWithBranchTypes = {
181+
branchTypes: [
182+
{ kind: 'Feature', prefix: 'feature/' },
183+
{ kind: 'Bugfix', prefix: 'bugfix/' },
184+
],
185+
} as any;
186+
187+
const mockRepoWithoutBranchTypes = {
188+
branchTypes: [],
189+
} as any;
190+
191+
const mockRepoWithUndefinedBranchTypes = {
192+
branchTypes: undefined,
193+
} as any;
194+
195+
it('should return first branch type when repo has branch types', () => {
196+
const result = getBranchTypeForRepo(mockRepoWithBranchTypes, []);
197+
expect(result).toEqual({ kind: 'Feature', prefix: 'feature/' });
198+
});
199+
200+
it('should return first custom prefix when repo has no branch types', () => {
201+
const customPrefixes = ['hotfix', 'release'];
202+
const result = getBranchTypeForRepo(mockRepoWithoutBranchTypes, customPrefixes);
203+
expect(result).toEqual({ kind: 'hotfix', prefix: 'hotfix/' });
204+
});
205+
206+
it('should normalize custom prefix by adding slash', () => {
207+
const customPrefixes = ['hotfix'];
208+
const result = getBranchTypeForRepo(mockRepoWithoutBranchTypes, customPrefixes);
209+
expect(result).toEqual({ kind: 'hotfix', prefix: 'hotfix/' });
210+
});
211+
212+
it('should not add slash if custom prefix already ends with slash', () => {
213+
const customPrefixes = ['hotfix/'];
214+
const result = getBranchTypeForRepo(mockRepoWithoutBranchTypes, customPrefixes);
215+
expect(result).toEqual({ kind: 'hotfix/', prefix: 'hotfix/' });
216+
});
217+
218+
it('should return empty branch type when no branch types and no custom prefixes', () => {
219+
const result = getBranchTypeForRepo(mockRepoWithoutBranchTypes, []);
220+
expect(result).toEqual({ kind: '', prefix: '' });
221+
});
222+
223+
it('should return empty branch type when branch types is undefined and no custom prefixes', () => {
224+
const result = getBranchTypeForRepo(mockRepoWithUndefinedBranchTypes, []);
225+
expect(result).toEqual({ kind: '', prefix: '' });
226+
});
227+
228+
it('should prioritize repo branch types over custom prefixes', () => {
229+
const customPrefixes = ['hotfix', 'release'];
230+
const result = getBranchTypeForRepo(mockRepoWithBranchTypes, customPrefixes);
231+
expect(result).toEqual({ kind: 'Feature', prefix: 'feature/' });
232+
});
157233
});
158234
});

src/react/atlascode/startwork/v3/utils/branchUtils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ export const generateBranchName = (
2727
issue: MinimalIssue<any>,
2828
customTemplate: string,
2929
): string => {
30+
// Use branchType if it has a prefix, otherwise use empty prefix
31+
const branchTypeToUse = branchType.prefix ? branchType : { kind: '', prefix: '' };
3032
const usernameBase = repo.userEmail
3133
? repo.userEmail
3234
.split('@')[0]
3335
.normalize('NFD') // Convert accented characters to two characters where the accent is separated out
3436
.replace(/[\u0300-\u036f]/g, '') // Remove the separated accent marks
3537
: 'username';
36-
const prefixBase = branchType.prefix.replace(/ /g, '-');
38+
const prefixBase = branchTypeToUse.prefix.replace(/ /g, '-');
3739
const summaryBase = issue.summary
3840
.substring(0, 50)
3941
.trim()
@@ -65,3 +67,20 @@ export const generateBranchName = (
6567
return 'Invalid template: please follow the format described above';
6668
}
6769
};
70+
71+
export const getBranchTypeForRepo = (repo: RepoData, customPrefixes: string[]): { kind: string; prefix: string } => {
72+
if (repo.branchTypes?.length > 0) {
73+
return repo.branchTypes[0];
74+
} else {
75+
const convertedCustomPrefixes = customPrefixes.map((prefix) => {
76+
const normalizedCustomPrefix = prefix.endsWith('/') ? prefix : prefix + '/';
77+
return { prefix: normalizedCustomPrefix, kind: prefix };
78+
});
79+
80+
if (convertedCustomPrefixes.length > 0) {
81+
return convertedCustomPrefixes[0];
82+
} else {
83+
return { kind: '', prefix: '' };
84+
}
85+
}
86+
};

0 commit comments

Comments
 (0)