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