]> source.dussan.org Git - sonarqube.git/blob
3a3a60974b762b390b5aec7a00b72dc5c0c8a819
[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 { within } from '@testing-library/react';
21 import userEvent from '@testing-library/user-event';
22 import { first, last } from 'lodash';
23 import selectEvent from 'react-select-event';
24 import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
25 import NewCodeDefinitionServiceMock from '../../../../api/mocks/NewCodeDefinitionServiceMock';
26 import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
27 import { mockComponent } from '../../../../helpers/mocks/component';
28 import { mockNewCodePeriodBranch } from '../../../../helpers/mocks/new-code-definition';
29 import { mockAppState } from '../../../../helpers/testMocks';
30 import {
31   RenderContext,
32   renderAppWithComponentContext,
33 } from '../../../../helpers/testReactTestingUtils';
34 import { byRole, byText } from '../../../../helpers/testSelector';
35 import { Feature } from '../../../../types/features';
36 import { NewCodeDefinitionType } from '../../../../types/new-code-definition';
37 import routes from '../../routes';
38
39 jest.mock('../../../../api/newCodeDefinition');
40 jest.mock('../../../../api/projectActivity');
41 jest.mock('../../../../api/branches');
42
43 const codePeriodsMock = new NewCodeDefinitionServiceMock();
44 const projectActivityMock = new ProjectActivityServiceMock();
45 const branchHandler = new BranchesServiceMock();
46
47 afterEach(() => {
48   branchHandler.reset();
49   codePeriodsMock.reset();
50   projectActivityMock.reset();
51 });
52
53 it('renders correctly without branch support feature', async () => {
54   const { ui } = getPageObjects();
55   renderProjectBaselineApp();
56   await ui.appIsLoaded();
57
58   expect(await ui.generalSettingRadio.find()).toBeChecked();
59   expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
60
61   // User is not admin
62   expect(ui.generalSettingsLink.query()).not.toBeInTheDocument();
63
64   // Specific branch setting is not rendered without feature branch
65   expect(ui.branchListHeading.query()).not.toBeInTheDocument();
66   expect(ui.referenceBranchRadio.query()).not.toBeInTheDocument();
67 });
68
69 it('prevents selection of global setting if it is not compliant and warns non-admin about it', async () => {
70   codePeriodsMock.setNewCodePeriod({
71     type: NewCodeDefinitionType.NumberOfDays,
72     value: '99',
73     inherited: true,
74   });
75
76   const { ui } = getPageObjects();
77   renderProjectBaselineApp();
78   await ui.appIsLoaded();
79
80   expect(await ui.generalSettingRadio.find()).toBeChecked();
81   expect(ui.generalSettingRadio.get()).toBeDisabled();
82   expect(ui.complianceWarning.get()).toBeVisible();
83 });
84
85 it('prevents selection of global setting if it is not compliant and warns admin about it', async () => {
86   codePeriodsMock.setNewCodePeriod({
87     type: NewCodeDefinitionType.NumberOfDays,
88     value: '99',
89     inherited: true,
90   });
91
92   const { ui } = getPageObjects();
93   renderProjectBaselineApp({ appState: mockAppState({ canAdmin: true }) });
94   await ui.appIsLoaded();
95
96   expect(await ui.generalSettingRadio.find()).toBeChecked();
97   expect(ui.generalSettingRadio.get()).toBeDisabled();
98   expect(ui.complianceWarningAdmin.get()).toBeVisible();
99   expect(ui.complianceWarning.query()).not.toBeInTheDocument();
100 });
101
102 it('renders correctly with branch support feature', async () => {
103   const { ui } = getPageObjects();
104   renderProjectBaselineApp({
105     featureList: [Feature.BranchSupport],
106     appState: mockAppState({ canAdmin: true }),
107   });
108   await ui.appIsLoaded();
109
110   expect(await ui.generalSettingRadio.find()).toBeChecked();
111   expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
112
113   // User is admin
114   expect(ui.generalSettingsLink.get()).toBeInTheDocument();
115
116   // Specific branch setting is rendered with feature support branch
117   expect(ui.branchListHeading.get()).toBeInTheDocument();
118   expect(ui.referenceBranchRadio.get()).toBeInTheDocument();
119 });
120
121 it('can set previous version specific setting', async () => {
122   const { ui, user } = getPageObjects();
123   renderProjectBaselineApp();
124   await ui.appIsLoaded();
125
126   expect(await ui.previousVersionRadio.find()).toHaveClass('disabled');
127   await ui.setPreviousVersionSetting();
128   expect(ui.previousVersionRadio.get()).toBeChecked();
129
130   // Save changes
131   await user.click(ui.saveButton.get());
132
133   expect(ui.saved.get()).toBeInTheDocument();
134
135   // Set general setting
136   await user.click(ui.generalSettingRadio.get());
137   expect(ui.previousVersionRadio.get()).toHaveClass('disabled');
138   await user.click(ui.saveButton.get());
139   expect(ui.saved.get()).toBeInTheDocument();
140 });
141
142 it('can set number of days specific setting', async () => {
143   const { ui, user } = getPageObjects();
144   renderProjectBaselineApp();
145   await ui.appIsLoaded();
146
147   expect(await ui.numberDaysRadio.find()).toHaveClass('disabled');
148   await ui.setNumberDaysSetting('10');
149   expect(ui.numberDaysRadio.get()).toBeChecked();
150
151   // Reset to initial state
152   await user.click(ui.cancelButton.get());
153   expect(ui.generalSettingRadio.get()).toBeChecked();
154   expect(ui.numberDaysRadio.get()).toHaveClass('disabled');
155
156   // Save changes
157   await ui.setNumberDaysSetting('10');
158   await user.click(ui.saveButton.get());
159
160   expect(ui.saved.get()).toBeInTheDocument();
161 });
162
163 it('can set reference branch specific setting', async () => {
164   const { ui, user } = getPageObjects();
165   renderProjectBaselineApp({
166     featureList: [Feature.BranchSupport],
167   });
168   await ui.appIsLoaded();
169
170   expect(await ui.referenceBranchRadio.find()).toHaveClass('disabled');
171   await ui.setReferenceBranchSetting('main');
172   expect(ui.referenceBranchRadio.get()).toBeChecked();
173
174   // Save changes
175   await user.click(ui.saveButton.get());
176
177   expect(ui.saved.get()).toBeInTheDocument();
178 });
179
180 it('cannot set specific analysis setting', async () => {
181   const { ui } = getPageObjects();
182   codePeriodsMock.setNewCodePeriod({
183     type: NewCodeDefinitionType.SpecificAnalysis,
184     value: 'analysis_id',
185   });
186   renderProjectBaselineApp();
187   await ui.appIsLoaded();
188
189   expect(await ui.specificAnalysisRadio.find()).toBeChecked();
190   expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
191   expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
192
193   await selectEvent.select(ui.analysisFromSelect.get(), 'baseline.branch_analyses.ranges.allTime');
194
195   expect(first(ui.analysisListItem.getAll())).toHaveClass('disabled');
196   expect(ui.saveButton.get()).toBeDisabled();
197 });
198
199 it('renders correctly branch modal', async () => {
200   const { ui } = getPageObjects();
201   renderProjectBaselineApp({
202     featureList: [Feature.BranchSupport],
203   });
204   await ui.appIsLoaded();
205
206   await ui.openBranchSettingModal('main');
207
208   expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
209 });
210
211 it('can set a previous version setting for branch', async () => {
212   const { ui, user } = getPageObjects();
213   renderProjectBaselineApp({
214     featureList: [Feature.BranchSupport],
215   });
216   await ui.appIsLoaded();
217   await ui.setBranchPreviousVersionSetting('main');
218
219   expect(
220     within(byRole('table').get()).getByText('new_code_definition.previous_version')
221   ).toBeInTheDocument();
222
223   await user.click(await ui.branchActionsButton('main').find());
224
225   expect(ui.resetToDefaultButton.get()).toBeInTheDocument();
226   await user.click(ui.resetToDefaultButton.get());
227
228   expect(
229     first(within(byRole('table').get()).getAllByText('branch_list.default_setting'))
230   ).toBeInTheDocument();
231 });
232
233 it('can set a number of days setting for branch', async () => {
234   const { ui } = getPageObjects();
235   renderProjectBaselineApp({
236     featureList: [Feature.BranchSupport],
237   });
238   await ui.appIsLoaded();
239
240   await ui.setBranchNumberOfDaysSetting('main', '15');
241
242   expect(
243     within(byRole('table').get()).getByText('new_code_definition.number_days: 15')
244   ).toBeInTheDocument();
245 });
246
247 it('cannot set a specific analysis setting for branch', async () => {
248   const { ui } = getPageObjects();
249   codePeriodsMock.setListBranchesNewCode([
250     mockNewCodePeriodBranch({
251       branchKey: 'main',
252       type: NewCodeDefinitionType.SpecificAnalysis,
253       value: 'analysis_id',
254     }),
255   ]);
256   renderProjectBaselineApp({
257     featureList: [Feature.BranchSupport],
258   });
259   await ui.appIsLoaded();
260
261   await ui.openBranchSettingModal('main');
262
263   expect(ui.specificAnalysisRadio.get()).toBeChecked();
264   expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
265   expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
266
267   await selectEvent.select(ui.analysisFromSelect.get(), 'baseline.branch_analyses.ranges.allTime');
268
269   expect(first(ui.analysisListItem.getAll())).toHaveClass('disabled');
270   expect(last(ui.saveButton.getAll())).toBeDisabled();
271 });
272
273 it('can set a reference branch setting for branch', async () => {
274   const { ui } = getPageObjects();
275   renderProjectBaselineApp({
276     featureList: [Feature.BranchSupport],
277   });
278   await ui.appIsLoaded();
279
280   await ui.setBranchReferenceToBranchSetting('main', 'normal-branch');
281
282   expect(
283     byRole('table').byText('baseline.reference_branch: normal-branch').get()
284   ).toBeInTheDocument();
285 });
286
287 function renderProjectBaselineApp(context: RenderContext = {}, params?: string) {
288   return renderAppWithComponentContext(
289     'baseline',
290     routes,
291     {
292       ...context,
293       navigateTo: params ? `baseline?id=my-project&${params}` : 'baseline?id=my-project',
294     },
295     {
296       component: mockComponent(),
297     }
298   );
299 }
300
301 function getPageObjects() {
302   const user = userEvent.setup();
303
304   const ui = {
305     pageHeading: byRole('heading', { name: 'project_baseline.page' }),
306     branchTableHeading: byText('branch_list.branch'),
307     branchListHeading: byRole('heading', { name: 'project_baseline.default_setting' }),
308     generalSettingsLink: byRole('link', { name: 'project_baseline.page.description2.link' }),
309     generalSettingRadio: byRole('radio', { name: 'project_baseline.global_setting' }),
310     specificSettingRadio: byRole('radio', { name: 'project_baseline.specific_setting' }),
311     previousVersionRadio: byRole('radio', {
312       name: /new_code_definition.previous_version.description/,
313     }),
314     numberDaysRadio: byRole('radio', { name: /new_code_definition.number_days.description/ }),
315     numberDaysInput: byRole('spinbutton'),
316     referenceBranchRadio: byRole('radio', { name: /baseline.reference_branch.description/ }),
317     chooseBranchSelect: byRole('combobox', { name: 'baseline.reference_branch.choose' }),
318     specificAnalysisRadio: byRole('radio', { name: /baseline.specific_analysis.description/ }),
319     specificAnalysisWarning: byText('baseline.specific_analysis.compliance_warning.title'),
320     analysisFromSelect: byRole('combobox', { name: 'baseline.analysis_from' }),
321     analysisListItem: byRole('radio', { name: /baseline.branch_analyses.analysis_for_x/ }),
322     saveButton: byRole('button', { name: 'save' }),
323     cancelButton: byRole('button', { name: 'cancel' }),
324     branchActionsButton: (branch: string) =>
325       byRole('button', { name: `branch_list.show_actions_for_x.${branch}` }),
326     editButton: byRole('button', { name: 'edit' }),
327     resetToDefaultButton: byRole('button', { name: 'reset_to_default' }),
328     saved: byText('settings.state.saved'),
329     complianceWarningAdmin: byText('new_code_definition.compliance.warning.explanation.admin'),
330     complianceWarning: byText('new_code_definition.compliance.warning.explanation'),
331   };
332
333   async function appIsLoaded() {
334     expect(await ui.pageHeading.find()).toBeInTheDocument();
335   }
336
337   async function setPreviousVersionSetting() {
338     await user.click(ui.specificSettingRadio.get());
339     await user.click(ui.previousVersionRadio.get());
340   }
341
342   async function setBranchPreviousVersionSetting(branch: string) {
343     await openBranchSettingModal(branch);
344     await user.click(last(ui.previousVersionRadio.getAll()) as HTMLElement);
345     await user.click(last(ui.saveButton.getAll()) as HTMLElement);
346   }
347
348   async function setNumberDaysSetting(value: string) {
349     await user.click(ui.specificSettingRadio.get());
350     await user.click(ui.numberDaysRadio.get());
351     await user.clear(ui.numberDaysInput.get());
352     await user.type(ui.numberDaysInput.get(), value);
353   }
354
355   async function setBranchNumberOfDaysSetting(branch: string, value: string) {
356     await openBranchSettingModal(branch);
357     await user.click(last(ui.numberDaysRadio.getAll()) as HTMLElement);
358     await user.clear(ui.numberDaysInput.get());
359     await user.type(ui.numberDaysInput.get(), value);
360     await user.click(last(ui.saveButton.getAll()) as HTMLElement);
361   }
362
363   async function setReferenceBranchSetting(branch: string) {
364     await user.click(ui.specificSettingRadio.get());
365     await user.click(ui.referenceBranchRadio.get());
366     await selectEvent.select(ui.chooseBranchSelect.get(), branch);
367   }
368
369   async function setBranchReferenceToBranchSetting(branch: string, branchRef: string) {
370     await openBranchSettingModal(branch);
371     await user.click(last(ui.referenceBranchRadio.getAll()) as HTMLElement);
372     await selectEvent.select(ui.chooseBranchSelect.get(), branchRef);
373     await user.click(last(ui.saveButton.getAll()) as HTMLElement);
374   }
375
376   async function openBranchSettingModal(branch: string) {
377     await user.click(await ui.branchActionsButton(branch).find());
378     await user.click(ui.editButton.get());
379   }
380
381   return {
382     ui: {
383       ...ui,
384       appIsLoaded,
385       setNumberDaysSetting,
386       setPreviousVersionSetting,
387       setReferenceBranchSetting,
388       setBranchPreviousVersionSetting,
389       setBranchNumberOfDaysSetting,
390       setBranchReferenceToBranchSetting,
391       openBranchSettingModal,
392     },
393     user,
394   };
395 }