]> source.dussan.org Git - sonarqube.git/blob
8758e89fc38de95e91fb730741e3ce8e73cb5cc9
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20
21 import userEvent from '@testing-library/user-event';
22 import { last } from 'lodash';
23 import { byLabelText, byRole, byText } from '~sonar-aligned/helpers/testSelector';
24 import { MessageTypes } from '../../../../api/messages';
25 import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
26 import MessagesServiceMock from '../../../../api/mocks/MessagesServiceMock';
27 import NewCodeDefinitionServiceMock from '../../../../api/mocks/NewCodeDefinitionServiceMock';
28 import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
29 import { mockComponent } from '../../../../helpers/mocks/component';
30 import { mockNewCodePeriodBranch } from '../../../../helpers/mocks/new-code-definition';
31 import { mockAnalysis } from '../../../../helpers/mocks/project-activity';
32 import { mockAppState } from '../../../../helpers/testMocks';
33 import {
34   RenderContext,
35   renderAppWithComponentContext,
36 } from '../../../../helpers/testReactTestingUtils';
37 import { Feature } from '../../../../types/features';
38 import { NewCodeDefinitionType } from '../../../../types/new-code-definition';
39 import routes from '../../routes';
40
41 jest.mock('../../../../api/newCodeDefinition');
42 jest.mock('../../../../api/projectActivity');
43 jest.mock('../../../../api/branches');
44
45 const newCodeDefinitionMock = new NewCodeDefinitionServiceMock();
46 const projectActivityMock = new ProjectActivityServiceMock();
47 const branchHandler = new BranchesServiceMock();
48 const messagesMock = new MessagesServiceMock();
49
50 afterEach(() => {
51   branchHandler.reset();
52   newCodeDefinitionMock.reset();
53   projectActivityMock.reset();
54   messagesMock.reset();
55 });
56
57 it('renders correctly without branch support feature', async () => {
58   const { ui } = getPageObjects();
59   renderProjectNewCodeDefinitionApp();
60   await ui.appIsLoaded();
61
62   expect(await ui.generalSettingRadio.find()).toBeChecked();
63   expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
64
65   // User is not admin
66   expect(ui.generalSettingsLink.query()).not.toBeInTheDocument();
67
68   expect(ui.referenceBranchRadio.query()).not.toBeInTheDocument();
69 });
70
71 it('renders correctly with branch support feature', async () => {
72   const { ui } = getPageObjects();
73   renderProjectNewCodeDefinitionApp({
74     featureList: [Feature.BranchSupport],
75     appState: mockAppState({ canAdmin: true }),
76   });
77   await ui.appIsLoaded();
78
79   expect(await ui.generalSettingRadio.find()).toBeChecked();
80   expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
81
82   // User is admin
83   expect(ui.generalSettingsLink.get()).toBeInTheDocument();
84
85   expect(ui.referenceBranchRadio.get()).toBeInTheDocument();
86 });
87
88 it('can set previous version specific setting', async () => {
89   const { ui, user } = getPageObjects();
90   renderProjectNewCodeDefinitionApp();
91   await ui.appIsLoaded();
92
93   expect(await ui.previousVersionRadio.find()).toHaveClass('disabled');
94   await ui.setPreviousVersionSetting();
95   expect(ui.previousVersionRadio.get()).toBeChecked();
96
97   // Save changes
98   await user.click(ui.saveButton.get());
99
100   expect(ui.saveButton.get()).toBeDisabled();
101
102   // Set general setting
103   await user.click(ui.generalSettingRadio.get());
104   expect(ui.previousVersionRadio.get()).toHaveClass('disabled');
105   await user.click(ui.saveButton.get());
106   expect(ui.saveButton.get()).toBeDisabled();
107 });
108
109 it('can set number of days specific setting', async () => {
110   const { ui, user } = getPageObjects();
111   renderProjectNewCodeDefinitionApp();
112   await ui.appIsLoaded();
113
114   expect(await ui.numberDaysRadio.find()).toHaveClass('disabled');
115   await ui.setNumberDaysSetting('10');
116   expect(ui.numberDaysRadio.get()).toBeChecked();
117
118   // Reset to initial state
119   await user.click(ui.cancelButton.get());
120   expect(ui.generalSettingRadio.get()).toBeChecked();
121   expect(ui.numberDaysRadio.get()).toHaveClass('disabled');
122
123   // Save changes
124   await ui.setNumberDaysSetting('10');
125   await user.click(ui.saveButton.get());
126
127   expect(ui.saveButton.get()).toBeDisabled();
128 });
129
130 it('can set reference branch specific setting', async () => {
131   const { ui, user } = getPageObjects();
132   renderProjectNewCodeDefinitionApp({
133     featureList: [Feature.BranchSupport],
134   });
135   await ui.appIsLoaded();
136
137   expect(await ui.referenceBranchRadio.find()).toHaveClass('disabled');
138   await ui.setReferenceBranchSetting('main');
139   expect(ui.referenceBranchRadio.get()).toBeChecked();
140
141   // Save changes
142   await user.click(ui.saveButton.get());
143
144   expect(ui.saveButton.get()).toBeDisabled();
145 });
146
147 it('cannot set specific analysis setting', async () => {
148   const { ui } = getPageObjects();
149   newCodeDefinitionMock.setListBranchesNewCode([
150     mockNewCodePeriodBranch({
151       branchKey: 'main',
152       type: NewCodeDefinitionType.SpecificAnalysis,
153       value: 'analysis_id',
154     }),
155   ]);
156   projectActivityMock.setAnalysesList([
157     mockAnalysis({
158       key: `analysis_id`,
159       date: '2018-01-11T00:00:00+0200',
160     }),
161   ]);
162   renderProjectNewCodeDefinitionApp();
163   await ui.appIsLoaded();
164
165   expect(await ui.specificAnalysisRadio.find()).toBeChecked();
166   expect(await ui.baselineSpecificAnalysisDate.find()).toBeInTheDocument();
167
168   expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
169   expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
170
171   expect(ui.saveButton.get()).toBeDisabled();
172 });
173
174 it('renders correctly branch modal', async () => {
175   const { ui } = getPageObjects();
176   renderProjectNewCodeDefinitionApp({
177     featureList: [Feature.BranchSupport],
178   });
179   await ui.appIsLoaded();
180
181   await ui.openBranchSettingModal('main');
182
183   expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
184 });
185
186 it('can set a previous version setting for branch', async () => {
187   const { ui, user } = getPageObjects();
188   renderProjectNewCodeDefinitionApp({
189     featureList: [Feature.BranchSupport],
190   });
191   await ui.appIsLoaded();
192   await ui.setBranchPreviousVersionSetting('main');
193
194   expect(
195     byRole('table').byRole('cell', { name: 'branch_list.default_setting' }).getAll(),
196   ).toHaveLength(2);
197   expect(byRole('table').byText('new_code_definition.previous_version').get()).toBeInTheDocument();
198
199   await user.click(await ui.branchActionsButton('main').find());
200
201   expect(ui.resetToDefaultButton.get()).toBeInTheDocument();
202   await user.click(ui.resetToDefaultButton.get());
203
204   expect(
205     byRole('table').byRole('cell', { name: 'branch_list.default_setting' }).getAll(),
206   ).toHaveLength(3);
207 });
208
209 it('can set a number of days setting for branch', async () => {
210   const { ui } = getPageObjects();
211   renderProjectNewCodeDefinitionApp({
212     featureList: [Feature.BranchSupport],
213   });
214   await ui.appIsLoaded();
215
216   await ui.setBranchNumberOfDaysSetting('main', '15');
217
218   expect(byRole('table').byText('new_code_definition.number_days: 15').get()).toBeInTheDocument();
219 });
220
221 it('cannot set a specific analysis setting for branch', async () => {
222   const { ui, user } = getPageObjects();
223   newCodeDefinitionMock.setListBranchesNewCode([
224     mockNewCodePeriodBranch({
225       branchKey: 'main',
226       type: NewCodeDefinitionType.SpecificAnalysis,
227       value: 'analysis_id',
228     }),
229   ]);
230   renderProjectNewCodeDefinitionApp({
231     featureList: [Feature.BranchSupport],
232   });
233   await ui.appIsLoaded();
234
235   await user.click(await byLabelText('branch_list.show_actions_for_x.main').find());
236   await user.click(await byRole('menuitem', { name: 'edit' }).find());
237   expect(ui.specificAnalysisRadio.get()).toBeChecked();
238   expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
239   expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
240
241   expect(last(ui.saveButton.getAll())).toBeDisabled();
242 });
243
244 it('can set a reference branch setting for branch', async () => {
245   const { ui } = getPageObjects();
246   renderProjectNewCodeDefinitionApp({
247     featureList: [Feature.BranchSupport],
248   });
249   await ui.appIsLoaded();
250
251   await ui.setBranchReferenceToBranchSetting('main', 'normal-branch');
252
253   expect(
254     byRole('table').byText('baseline.reference_branch: normal-branch').get(),
255   ).toBeInTheDocument();
256 });
257
258 it('should display NCD banner if some branches had their NCD automatically changed', async () => {
259   const { ui } = getPageObjects();
260
261   newCodeDefinitionMock.setListBranchesNewCode([
262     {
263       projectKey: 'test-project:test',
264       branchKey: 'test-branch',
265       type: NewCodeDefinitionType.NumberOfDays,
266       value: '25',
267       inherited: true,
268       updatedAt: 1692720953662,
269     },
270     {
271       projectKey: 'test-project:test',
272       branchKey: 'master',
273       type: NewCodeDefinitionType.NumberOfDays,
274       value: '32',
275       previousNonCompliantValue: '150',
276       updatedAt: 1692721852743,
277     },
278   ]);
279
280   renderProjectNewCodeDefinitionApp({
281     featureList: [Feature.BranchSupport],
282   });
283
284   expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
285   expect(
286     ui.branchNCDsBanner.byText('new_code_definition.auto_update.branch.list_itemmaster32150').get(),
287   ).toBeInTheDocument();
288 });
289
290 it('should not display NCD banner if some branches had their NCD automatically changed and banne has been dismissed', async () => {
291   const { ui } = getPageObjects();
292
293   newCodeDefinitionMock.setListBranchesNewCode([
294     {
295       projectKey: 'test-project:test',
296       branchKey: 'test-branch',
297       type: NewCodeDefinitionType.NumberOfDays,
298       value: '25',
299       inherited: true,
300       updatedAt: 1692720953662,
301     },
302     {
303       projectKey: 'test-project:test',
304       branchKey: 'master',
305       type: NewCodeDefinitionType.NumberOfDays,
306       value: '32',
307       previousNonCompliantValue: '150',
308       updatedAt: 1692721852743,
309     },
310   ]);
311   messagesMock.setMessageDismissed({
312     projectKey: 'test-project:test',
313     messageType: MessageTypes.BranchNcd90,
314   });
315
316   renderProjectNewCodeDefinitionApp({
317     featureList: [Feature.BranchSupport],
318   });
319
320   expect(await ui.branchNCDsBanner.query()).not.toBeInTheDocument();
321 });
322
323 it('should correctly dismiss branch banner', async () => {
324   const { ui } = getPageObjects();
325
326   newCodeDefinitionMock.setListBranchesNewCode([
327     {
328       projectKey: 'test-project:test',
329       branchKey: 'test-branch',
330       type: NewCodeDefinitionType.NumberOfDays,
331       value: '25',
332       inherited: true,
333       updatedAt: 1692720953662,
334     },
335     {
336       projectKey: 'test-project:test',
337       branchKey: 'master',
338       type: NewCodeDefinitionType.NumberOfDays,
339       value: '32',
340       previousNonCompliantValue: '150',
341       updatedAt: 1692721852743,
342     },
343   ]);
344
345   renderProjectNewCodeDefinitionApp({
346     featureList: [Feature.BranchSupport],
347   });
348
349   expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
350
351   const user = userEvent.setup();
352   await user.click(ui.dismissButton.get());
353
354   expect(ui.branchNCDsBanner.query()).not.toBeInTheDocument();
355 });
356
357 function renderProjectNewCodeDefinitionApp(context: RenderContext = {}, params?: string) {
358   return renderAppWithComponentContext(
359     'baseline',
360     routes,
361     {
362       ...context,
363       navigateTo: params ? `baseline?id=my-project&${params}` : 'baseline?id=my-project',
364     },
365     {
366       component: mockComponent(),
367     },
368   );
369 }
370
371 function getPageObjects() {
372   const user = userEvent.setup();
373
374   const ui = {
375     pageHeading: byRole('heading', { name: 'project_baseline.page' }),
376     branchTableHeading: byText('branch_list.branch'),
377     generalSettingsLink: byRole('link', { name: 'project_baseline.page.description2.link' }),
378     generalSettingRadio: byRole('radio', { name: 'project_baseline.global_setting' }),
379     specificSettingRadio: byRole('radio', { name: 'project_baseline.specific_setting' }),
380     previousVersionRadio: byRole('radio', {
381       name: /new_code_definition.previous_version.description/,
382     }),
383     numberDaysRadio: byRole('radio', { name: /new_code_definition.number_days.description/ }),
384     numberDaysInput: byRole('spinbutton'),
385     referenceBranchRadio: byRole('radio', { name: /baseline.reference_branch.description/ }),
386     chooseBranchSelect: byRole('combobox', { name: 'baseline.reference_branch.choose' }),
387     specificAnalysisRadio: byRole('radio', { name: /baseline.specific_analysis.description/ }),
388     specificAnalysisWarning: byText('baseline.specific_analysis.compliance_warning.title'),
389     saveButton: byRole('button', { name: 'save' }),
390     cancelButton: byRole('button', { name: 'cancel' }),
391     branchActionsButton: (name: string) =>
392       byRole('button', { name: `branch_list.show_actions_for_x.${name}` }),
393     resetToDefaultButton: byRole('menuitem', { name: 'reset_to_default' }),
394     branchNCDsBanner: byText(/new_code_definition.auto_update.branch.message/),
395     dismissButton: byLabelText('dismiss'),
396     baselineSpecificAnalysisDate: byText(/January 10, 2018/),
397   };
398
399   async function appIsLoaded() {
400     expect(await ui.pageHeading.find()).toBeInTheDocument();
401   }
402
403   async function setPreviousVersionSetting() {
404     await user.click(ui.specificSettingRadio.get());
405     await user.click(ui.previousVersionRadio.get());
406   }
407
408   async function setBranchPreviousVersionSetting(branch: string) {
409     await openBranchSettingModal(branch);
410     await user.click(last(ui.previousVersionRadio.getAll()) as HTMLElement);
411     await user.click(last(ui.saveButton.getAll()) as HTMLElement);
412   }
413
414   async function setNumberDaysSetting(value: string) {
415     await user.click(ui.specificSettingRadio.get());
416     await user.click(ui.numberDaysRadio.get());
417     await user.clear(ui.numberDaysInput.get());
418     await user.type(ui.numberDaysInput.get(), value);
419   }
420
421   async function setBranchNumberOfDaysSetting(branch: string, value: string) {
422     await openBranchSettingModal(branch);
423     await user.click(last(ui.numberDaysRadio.getAll()) as HTMLElement);
424     await user.clear(ui.numberDaysInput.get());
425     await user.type(ui.numberDaysInput.get(), value);
426     await user.click(last(ui.saveButton.getAll()) as HTMLElement);
427   }
428
429   async function setReferenceBranchSetting(branch: string) {
430     await user.click(ui.specificSettingRadio.get());
431     await user.click(ui.referenceBranchRadio.get());
432
433     await user.click(ui.chooseBranchSelect.get());
434     await user.click(byRole('option', { name: new RegExp(branch) }).get());
435   }
436
437   async function setBranchReferenceToBranchSetting(branch: string, branchRef: string) {
438     await openBranchSettingModal(branch);
439     await user.click(last(ui.referenceBranchRadio.getAll()) as HTMLElement);
440
441     await user.click(ui.chooseBranchSelect.get());
442     await user.click(byRole('option', { name: new RegExp(branchRef) }).get());
443
444     await user.click(last(ui.saveButton.getAll()) as HTMLElement);
445   }
446
447   async function openBranchSettingModal(branch: string) {
448     await user.click(await byLabelText(`branch_list.edit_for_x.${branch}`).find());
449   }
450
451   return {
452     ui: {
453       ...ui,
454       appIsLoaded,
455       setNumberDaysSetting,
456       setPreviousVersionSetting,
457       setReferenceBranchSetting,
458       setBranchPreviousVersionSetting,
459       setBranchNumberOfDaysSetting,
460       setBranchReferenceToBranchSetting,
461       openBranchSettingModal,
462     },
463     user,
464   };
465 }