3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 import { act, 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 { 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 { mockAppState } from '../../../../helpers/testMocks';
34 renderAppWithComponentContext,
35 } from '../../../../helpers/testReactTestingUtils';
36 import { byLabelText, byRole, byText } from '../../../../helpers/testSelector';
37 import { Feature } from '../../../../types/features';
38 import { NewCodeDefinitionType } from '../../../../types/new-code-definition';
39 import routes from '../../routes';
41 jest.mock('../../../../api/newCodeDefinition');
42 jest.mock('../../../../api/projectActivity');
43 jest.mock('../../../../api/branches');
45 const newCodeDefinitionMock = new NewCodeDefinitionServiceMock();
46 const projectActivityMock = new ProjectActivityServiceMock();
47 const branchHandler = new BranchesServiceMock();
48 const messagesMock = new MessagesServiceMock();
51 branchHandler.reset();
52 newCodeDefinitionMock.reset();
53 projectActivityMock.reset();
57 it('renders correctly without branch support feature', async () => {
58 const { ui } = getPageObjects();
59 renderProjectNewCodeDefinitionApp();
60 await ui.appIsLoaded();
62 expect(await ui.generalSettingRadio.find()).toBeChecked();
63 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
66 expect(ui.generalSettingsLink.query()).not.toBeInTheDocument();
68 // Specific branch setting is not rendered without feature branch
69 expect(ui.branchListHeading.query()).not.toBeInTheDocument();
70 expect(ui.referenceBranchRadio.query()).not.toBeInTheDocument();
73 it('prevents selection of global setting if it is not compliant and warns non-admin about it', async () => {
74 newCodeDefinitionMock.setNewCodePeriod({
75 type: NewCodeDefinitionType.NumberOfDays,
80 const { ui } = getPageObjects();
81 renderProjectNewCodeDefinitionApp();
82 await ui.appIsLoaded();
84 expect(await ui.generalSettingRadio.find()).toBeChecked();
85 expect(ui.generalSettingRadio.get()).toBeDisabled();
86 expect(ui.complianceWarning.get()).toBeVisible();
89 it('prevents selection of global setting if it is not compliant and warns admin about it', async () => {
90 newCodeDefinitionMock.setNewCodePeriod({
91 type: NewCodeDefinitionType.NumberOfDays,
96 const { ui } = getPageObjects();
97 renderProjectNewCodeDefinitionApp({ appState: mockAppState({ canAdmin: true }) });
98 await ui.appIsLoaded();
100 expect(await ui.generalSettingRadio.find()).toBeChecked();
101 expect(ui.generalSettingRadio.get()).toBeDisabled();
102 expect(ui.complianceWarningAdmin.get()).toBeVisible();
103 expect(ui.complianceWarning.query()).not.toBeInTheDocument();
106 it('renders correctly with branch support feature', async () => {
107 const { ui } = getPageObjects();
108 renderProjectNewCodeDefinitionApp({
109 featureList: [Feature.BranchSupport],
110 appState: mockAppState({ canAdmin: true }),
112 await ui.appIsLoaded();
114 expect(await ui.generalSettingRadio.find()).toBeChecked();
115 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
118 expect(ui.generalSettingsLink.get()).toBeInTheDocument();
120 // Specific branch setting is rendered with feature support branch
121 expect(ui.branchListHeading.get()).toBeInTheDocument();
122 expect(ui.referenceBranchRadio.get()).toBeInTheDocument();
125 it('can set previous version specific setting', async () => {
126 const { ui, user } = getPageObjects();
127 renderProjectNewCodeDefinitionApp();
128 await ui.appIsLoaded();
130 expect(await ui.previousVersionRadio.find()).toHaveClass('disabled');
131 await ui.setPreviousVersionSetting();
132 expect(ui.previousVersionRadio.get()).toBeChecked();
135 await user.click(ui.saveButton.get());
137 expect(ui.saved.get()).toBeInTheDocument();
139 // Set general setting
140 await user.click(ui.generalSettingRadio.get());
141 expect(ui.previousVersionRadio.get()).toHaveClass('disabled');
142 await user.click(ui.saveButton.get());
143 expect(ui.saved.get()).toBeInTheDocument();
146 it('can set number of days specific setting', async () => {
147 const { ui, user } = getPageObjects();
148 renderProjectNewCodeDefinitionApp();
149 await ui.appIsLoaded();
151 expect(await ui.numberDaysRadio.find()).toHaveClass('disabled');
152 await ui.setNumberDaysSetting('10');
153 expect(ui.numberDaysRadio.get()).toBeChecked();
155 // Reset to initial state
156 await user.click(ui.cancelButton.get());
157 expect(ui.generalSettingRadio.get()).toBeChecked();
158 expect(ui.numberDaysRadio.get()).toHaveClass('disabled');
161 await ui.setNumberDaysSetting('10');
162 await user.click(ui.saveButton.get());
164 expect(ui.saved.get()).toBeInTheDocument();
167 it('can set reference branch specific setting', async () => {
168 const { ui, user } = getPageObjects();
169 renderProjectNewCodeDefinitionApp({
170 featureList: [Feature.BranchSupport],
172 await ui.appIsLoaded();
174 expect(await ui.referenceBranchRadio.find()).toHaveClass('disabled');
175 await ui.setReferenceBranchSetting('main');
176 expect(ui.referenceBranchRadio.get()).toBeChecked();
179 await user.click(ui.saveButton.get());
181 expect(ui.saved.get()).toBeInTheDocument();
184 it('cannot set specific analysis setting', async () => {
185 const { ui } = getPageObjects();
186 newCodeDefinitionMock.setNewCodePeriod({
187 type: NewCodeDefinitionType.SpecificAnalysis,
188 value: 'analysis_id',
190 renderProjectNewCodeDefinitionApp();
191 await ui.appIsLoaded();
193 expect(await ui.specificAnalysisRadio.find()).toBeChecked();
194 expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
195 expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
197 await selectEvent.select(ui.analysisFromSelect.get(), 'baseline.branch_analyses.ranges.allTime');
199 expect(first(ui.analysisListItem.getAll())).toHaveClass('disabled');
200 expect(ui.saveButton.get()).toBeDisabled();
203 it('renders correctly branch modal', async () => {
204 const { ui } = getPageObjects();
205 renderProjectNewCodeDefinitionApp({
206 featureList: [Feature.BranchSupport],
208 await ui.appIsLoaded();
210 await ui.openBranchSettingModal('main');
212 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
215 it('can set a previous version setting for branch', async () => {
216 const { ui, user } = getPageObjects();
217 renderProjectNewCodeDefinitionApp({
218 featureList: [Feature.BranchSupport],
220 await ui.appIsLoaded();
221 await ui.setBranchPreviousVersionSetting('main');
224 within(byRole('table').get()).getByText('new_code_definition.previous_version')
225 ).toBeInTheDocument();
227 await user.click(await ui.branchActionsButton('main').find());
229 expect(ui.resetToDefaultButton.get()).toBeInTheDocument();
230 await user.click(ui.resetToDefaultButton.get());
233 first(within(byRole('table').get()).getAllByText('branch_list.default_setting'))
234 ).toBeInTheDocument();
237 it('can set a number of days setting for branch', async () => {
238 const { ui } = getPageObjects();
239 renderProjectNewCodeDefinitionApp({
240 featureList: [Feature.BranchSupport],
242 await ui.appIsLoaded();
244 await ui.setBranchNumberOfDaysSetting('main', '15');
247 within(byRole('table').get()).getByText('new_code_definition.number_days: 15')
248 ).toBeInTheDocument();
251 it('cannot set a specific analysis setting for branch', async () => {
252 const { ui } = getPageObjects();
253 newCodeDefinitionMock.setListBranchesNewCode([
254 mockNewCodePeriodBranch({
256 type: NewCodeDefinitionType.SpecificAnalysis,
257 value: 'analysis_id',
260 renderProjectNewCodeDefinitionApp({
261 featureList: [Feature.BranchSupport],
263 await ui.appIsLoaded();
265 await ui.openBranchSettingModal('main');
267 expect(ui.specificAnalysisRadio.get()).toBeChecked();
268 expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
269 expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
271 await selectEvent.select(ui.analysisFromSelect.get(), 'baseline.branch_analyses.ranges.allTime');
273 expect(first(ui.analysisListItem.getAll())).toHaveClass('disabled');
274 expect(last(ui.saveButton.getAll())).toBeDisabled();
277 it('can set a reference branch setting for branch', async () => {
278 const { ui } = getPageObjects();
279 renderProjectNewCodeDefinitionApp({
280 featureList: [Feature.BranchSupport],
282 await ui.appIsLoaded();
284 await ui.setBranchReferenceToBranchSetting('main', 'normal-branch');
287 byRole('table').byText('baseline.reference_branch: normal-branch').get()
288 ).toBeInTheDocument();
291 it('should display NCD banner if some branches had their NCD automatically changed', async () => {
292 const { ui } = getPageObjects();
294 newCodeDefinitionMock.setListBranchesNewCode([
296 projectKey: 'test-project:test',
297 branchKey: 'test-branch',
298 type: NewCodeDefinitionType.NumberOfDays,
301 updatedAt: 1692720953662,
304 projectKey: 'test-project:test',
306 type: NewCodeDefinitionType.NumberOfDays,
308 previousNonCompliantValue: '150',
309 updatedAt: 1692721852743,
313 renderProjectNewCodeDefinitionApp({
314 featureList: [Feature.BranchSupport],
317 expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
319 ui.branchNCDsBanner.byText('new_code_definition.auto_update.branch.list_itemmaster32150').get()
320 ).toBeInTheDocument();
323 it('should not display NCD banner if some branches had their NCD automatically changed and banne has been dismissed', async () => {
324 const { ui } = getPageObjects();
326 newCodeDefinitionMock.setListBranchesNewCode([
328 projectKey: 'test-project:test',
329 branchKey: 'test-branch',
330 type: NewCodeDefinitionType.NumberOfDays,
333 updatedAt: 1692720953662,
336 projectKey: 'test-project:test',
338 type: NewCodeDefinitionType.NumberOfDays,
340 previousNonCompliantValue: '150',
341 updatedAt: 1692721852743,
344 messagesMock.setMessageDismissed({
345 projectKey: 'test-project:test',
346 messageType: MessageTypes.BranchNcd90,
349 renderProjectNewCodeDefinitionApp({
350 featureList: [Feature.BranchSupport],
353 expect(await ui.branchNCDsBanner.query()).not.toBeInTheDocument();
356 it('should correctly dismiss branch banner', async () => {
357 const { ui } = getPageObjects();
359 newCodeDefinitionMock.setListBranchesNewCode([
361 projectKey: 'test-project:test',
362 branchKey: 'test-branch',
363 type: NewCodeDefinitionType.NumberOfDays,
366 updatedAt: 1692720953662,
369 projectKey: 'test-project:test',
371 type: NewCodeDefinitionType.NumberOfDays,
373 previousNonCompliantValue: '150',
374 updatedAt: 1692721852743,
378 renderProjectNewCodeDefinitionApp({
379 featureList: [Feature.BranchSupport],
382 expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
384 const user = userEvent.setup();
385 await act(async () => {
386 await user.click(ui.dismissButton.get());
389 expect(ui.branchNCDsBanner.query()).not.toBeInTheDocument();
392 function renderProjectNewCodeDefinitionApp(context: RenderContext = {}, params?: string) {
393 return renderAppWithComponentContext(
398 navigateTo: params ? `baseline?id=my-project&${params}` : 'baseline?id=my-project',
401 component: mockComponent(),
406 function getPageObjects() {
407 const user = userEvent.setup();
410 pageHeading: byRole('heading', { name: 'project_baseline.page' }),
411 branchTableHeading: byText('branch_list.branch'),
412 branchListHeading: byRole('heading', { name: 'project_baseline.default_setting' }),
413 generalSettingsLink: byRole('link', { name: 'project_baseline.page.description2.link' }),
414 generalSettingRadio: byRole('radio', { name: 'project_baseline.global_setting' }),
415 specificSettingRadio: byRole('radio', { name: 'project_baseline.specific_setting' }),
416 previousVersionRadio: byRole('radio', {
417 name: /new_code_definition.previous_version.description/,
419 numberDaysRadio: byRole('radio', { name: /new_code_definition.number_days.description/ }),
420 numberDaysInput: byRole('spinbutton'),
421 referenceBranchRadio: byRole('radio', { name: /baseline.reference_branch.description/ }),
422 chooseBranchSelect: byRole('combobox', { name: 'baseline.reference_branch.choose' }),
423 specificAnalysisRadio: byRole('radio', { name: /baseline.specific_analysis.description/ }),
424 specificAnalysisWarning: byText('baseline.specific_analysis.compliance_warning.title'),
425 analysisFromSelect: byRole('combobox', { name: 'baseline.analysis_from' }),
426 analysisListItem: byRole('radio', { name: /baseline.branch_analyses.analysis_for_x/ }),
427 saveButton: byRole('button', { name: 'save' }),
428 cancelButton: byRole('button', { name: 'cancel' }),
429 branchActionsButton: (branch: string) =>
430 byRole('button', { name: `branch_list.show_actions_for_x.${branch}` }),
431 editButton: byRole('button', { name: 'edit' }),
432 resetToDefaultButton: byRole('button', { name: 'reset_to_default' }),
433 saved: byText('settings.state.saved'),
434 complianceWarningAdmin: byText('new_code_definition.compliance.warning.explanation.admin'),
435 complianceWarning: byText('new_code_definition.compliance.warning.explanation'),
436 branchNCDsBanner: byText(/new_code_definition.auto_update.branch.message/),
437 dismissButton: byLabelText('alert.dismiss'),
440 async function appIsLoaded() {
441 expect(await ui.pageHeading.find()).toBeInTheDocument();
444 async function setPreviousVersionSetting() {
445 await user.click(ui.specificSettingRadio.get());
446 await user.click(ui.previousVersionRadio.get());
449 async function setBranchPreviousVersionSetting(branch: string) {
450 await openBranchSettingModal(branch);
451 await user.click(last(ui.previousVersionRadio.getAll()) as HTMLElement);
452 await user.click(last(ui.saveButton.getAll()) as HTMLElement);
455 async function setNumberDaysSetting(value: string) {
456 await user.click(ui.specificSettingRadio.get());
457 await user.click(ui.numberDaysRadio.get());
458 await user.clear(ui.numberDaysInput.get());
459 await user.type(ui.numberDaysInput.get(), value);
462 async function setBranchNumberOfDaysSetting(branch: string, value: string) {
463 await openBranchSettingModal(branch);
464 await user.click(last(ui.numberDaysRadio.getAll()) as HTMLElement);
465 await user.clear(ui.numberDaysInput.get());
466 await user.type(ui.numberDaysInput.get(), value);
467 await user.click(last(ui.saveButton.getAll()) as HTMLElement);
470 async function setReferenceBranchSetting(branch: string) {
471 await user.click(ui.specificSettingRadio.get());
472 await user.click(ui.referenceBranchRadio.get());
473 await selectEvent.select(ui.chooseBranchSelect.get(), branch);
476 async function setBranchReferenceToBranchSetting(branch: string, branchRef: string) {
477 await openBranchSettingModal(branch);
478 await user.click(last(ui.referenceBranchRadio.getAll()) as HTMLElement);
479 await selectEvent.select(ui.chooseBranchSelect.get(), branchRef);
480 await user.click(last(ui.saveButton.getAll()) as HTMLElement);
483 async function openBranchSettingModal(branch: string) {
484 await user.click(await ui.branchActionsButton(branch).find());
485 await user.click(ui.editButton.get());
492 setNumberDaysSetting,
493 setPreviousVersionSetting,
494 setReferenceBranchSetting,
495 setBranchPreviousVersionSetting,
496 setBranchNumberOfDaysSetting,
497 setBranchReferenceToBranchSetting,
498 openBranchSettingModal,