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('renders correctly with branch support feature', async () => {
74 const { ui } = getPageObjects();
75 renderProjectNewCodeDefinitionApp({
76 featureList: [Feature.BranchSupport],
77 appState: mockAppState({ canAdmin: true }),
79 await ui.appIsLoaded();
81 expect(await ui.generalSettingRadio.find()).toBeChecked();
82 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
85 expect(ui.generalSettingsLink.get()).toBeInTheDocument();
87 // Specific branch setting is rendered with feature support branch
88 expect(ui.branchListHeading.get()).toBeInTheDocument();
89 expect(ui.referenceBranchRadio.get()).toBeInTheDocument();
92 it('can set previous version specific setting', async () => {
93 const { ui, user } = getPageObjects();
94 renderProjectNewCodeDefinitionApp();
95 await ui.appIsLoaded();
97 expect(await ui.previousVersionRadio.find()).toHaveClass('disabled');
98 await ui.setPreviousVersionSetting();
99 expect(ui.previousVersionRadio.get()).toBeChecked();
102 await user.click(ui.saveButton.get());
104 expect(ui.saveButton.get()).toBeDisabled();
106 // Set general setting
107 await user.click(ui.generalSettingRadio.get());
108 expect(ui.previousVersionRadio.get()).toHaveClass('disabled');
109 await user.click(ui.saveButton.get());
110 expect(ui.saveButton.get()).toBeDisabled();
113 it('can set number of days specific setting', async () => {
114 const { ui, user } = getPageObjects();
115 renderProjectNewCodeDefinitionApp();
116 await ui.appIsLoaded();
118 expect(await ui.numberDaysRadio.find()).toHaveClass('disabled');
119 await ui.setNumberDaysSetting('10');
120 expect(ui.numberDaysRadio.get()).toBeChecked();
122 // Reset to initial state
123 await user.click(ui.cancelButton.get());
124 expect(ui.generalSettingRadio.get()).toBeChecked();
125 expect(ui.numberDaysRadio.get()).toHaveClass('disabled');
128 await ui.setNumberDaysSetting('10');
129 await user.click(ui.saveButton.get());
131 expect(ui.saveButton.get()).toBeDisabled();
134 it('can set reference branch specific setting', async () => {
135 const { ui, user } = getPageObjects();
136 renderProjectNewCodeDefinitionApp({
137 featureList: [Feature.BranchSupport],
139 await ui.appIsLoaded();
141 expect(await ui.referenceBranchRadio.find()).toHaveClass('disabled');
142 await ui.setReferenceBranchSetting('main');
143 expect(ui.referenceBranchRadio.get()).toBeChecked();
146 await user.click(ui.saveButton.get());
148 expect(ui.saveButton.get()).toBeDisabled();
151 it('cannot set specific analysis setting', async () => {
152 const { ui } = getPageObjects();
153 newCodeDefinitionMock.setListBranchesNewCode([
154 mockNewCodePeriodBranch({
156 type: NewCodeDefinitionType.SpecificAnalysis,
157 value: 'analysis_id',
160 renderProjectNewCodeDefinitionApp();
161 await ui.appIsLoaded();
163 expect(await ui.specificAnalysisRadio.find()).toBeChecked();
164 expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
165 expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
167 await selectEvent.select(ui.analysisFromSelect.get(), 'baseline.branch_analyses.ranges.allTime');
169 expect(first(ui.analysisListItem.getAll())).toHaveClass('disabled');
170 expect(ui.saveButton.get()).toBeDisabled();
173 it('renders correctly branch modal', async () => {
174 const { ui } = getPageObjects();
175 renderProjectNewCodeDefinitionApp({
176 featureList: [Feature.BranchSupport],
178 await ui.appIsLoaded();
180 await ui.openBranchSettingModal('main');
182 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
185 it('can set a previous version setting for branch', async () => {
186 const { ui, user } = getPageObjects();
187 renderProjectNewCodeDefinitionApp({
188 featureList: [Feature.BranchSupport],
190 await ui.appIsLoaded();
191 await ui.setBranchPreviousVersionSetting('main');
194 within(byRole('table').get()).getByText('new_code_definition.previous_version'),
195 ).toBeInTheDocument();
197 await user.click(await ui.branchActionsButton('main').find());
199 expect(ui.resetToDefaultButton.get()).toBeInTheDocument();
200 await user.click(ui.resetToDefaultButton.get());
203 first(within(byRole('table').get()).getAllByText('branch_list.default_setting')),
204 ).toBeInTheDocument();
207 it('can set a number of days setting for branch', async () => {
208 const { ui } = getPageObjects();
209 renderProjectNewCodeDefinitionApp({
210 featureList: [Feature.BranchSupport],
212 await ui.appIsLoaded();
214 await ui.setBranchNumberOfDaysSetting('main', '15');
217 within(byRole('table').get()).getByText('new_code_definition.number_days: 15'),
218 ).toBeInTheDocument();
221 it('cannot set a specific analysis setting for branch', async () => {
222 const { ui } = getPageObjects();
223 newCodeDefinitionMock.setListBranchesNewCode([
224 mockNewCodePeriodBranch({
226 type: NewCodeDefinitionType.SpecificAnalysis,
227 value: 'analysis_id',
230 renderProjectNewCodeDefinitionApp({
231 featureList: [Feature.BranchSupport],
233 await ui.appIsLoaded();
235 await ui.openBranchSettingModal('main');
237 expect(ui.specificAnalysisRadio.get()).toBeChecked();
238 expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
239 expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
241 await selectEvent.select(ui.analysisFromSelect.get(), 'baseline.branch_analyses.ranges.allTime');
243 expect(first(ui.analysisListItem.getAll())).toHaveClass('disabled');
244 expect(last(ui.saveButton.getAll())).toBeDisabled();
247 it('can set a reference branch setting for branch', async () => {
248 const { ui } = getPageObjects();
249 renderProjectNewCodeDefinitionApp({
250 featureList: [Feature.BranchSupport],
252 await ui.appIsLoaded();
254 await ui.setBranchReferenceToBranchSetting('main', 'normal-branch');
257 byRole('table').byText('baseline.reference_branch: normal-branch').get(),
258 ).toBeInTheDocument();
261 it('should display NCD banner if some branches had their NCD automatically changed', async () => {
262 const { ui } = getPageObjects();
264 newCodeDefinitionMock.setListBranchesNewCode([
266 projectKey: 'test-project:test',
267 branchKey: 'test-branch',
268 type: NewCodeDefinitionType.NumberOfDays,
271 updatedAt: 1692720953662,
274 projectKey: 'test-project:test',
276 type: NewCodeDefinitionType.NumberOfDays,
278 previousNonCompliantValue: '150',
279 updatedAt: 1692721852743,
283 renderProjectNewCodeDefinitionApp({
284 featureList: [Feature.BranchSupport],
287 expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
289 ui.branchNCDsBanner.byText('new_code_definition.auto_update.branch.list_itemmaster32150').get(),
290 ).toBeInTheDocument();
293 it('should not display NCD banner if some branches had their NCD automatically changed and banne has been dismissed', async () => {
294 const { ui } = getPageObjects();
296 newCodeDefinitionMock.setListBranchesNewCode([
298 projectKey: 'test-project:test',
299 branchKey: 'test-branch',
300 type: NewCodeDefinitionType.NumberOfDays,
303 updatedAt: 1692720953662,
306 projectKey: 'test-project:test',
308 type: NewCodeDefinitionType.NumberOfDays,
310 previousNonCompliantValue: '150',
311 updatedAt: 1692721852743,
314 messagesMock.setMessageDismissed({
315 projectKey: 'test-project:test',
316 messageType: MessageTypes.BranchNcd90,
319 renderProjectNewCodeDefinitionApp({
320 featureList: [Feature.BranchSupport],
323 expect(await ui.branchNCDsBanner.query()).not.toBeInTheDocument();
326 it('should correctly dismiss branch banner', async () => {
327 const { ui } = getPageObjects();
329 newCodeDefinitionMock.setListBranchesNewCode([
331 projectKey: 'test-project:test',
332 branchKey: 'test-branch',
333 type: NewCodeDefinitionType.NumberOfDays,
336 updatedAt: 1692720953662,
339 projectKey: 'test-project:test',
341 type: NewCodeDefinitionType.NumberOfDays,
343 previousNonCompliantValue: '150',
344 updatedAt: 1692721852743,
348 renderProjectNewCodeDefinitionApp({
349 featureList: [Feature.BranchSupport],
352 expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
354 const user = userEvent.setup();
355 await act(async () => {
356 await user.click(ui.dismissButton.get());
359 expect(ui.branchNCDsBanner.query()).not.toBeInTheDocument();
362 function renderProjectNewCodeDefinitionApp(context: RenderContext = {}, params?: string) {
363 return renderAppWithComponentContext(
368 navigateTo: params ? `baseline?id=my-project&${params}` : 'baseline?id=my-project',
371 component: mockComponent(),
376 function getPageObjects() {
377 const user = userEvent.setup();
380 pageHeading: byRole('heading', { name: 'project_baseline.page' }),
381 branchTableHeading: byText('branch_list.branch'),
382 branchListHeading: byRole('heading', { name: 'project_baseline.default_setting' }),
383 generalSettingsLink: byRole('link', { name: 'project_baseline.page.description2.link' }),
384 generalSettingRadio: byRole('radio', { name: 'project_baseline.global_setting' }),
385 specificSettingRadio: byRole('radio', { name: 'project_baseline.specific_setting' }),
386 previousVersionRadio: byRole('radio', {
387 name: /new_code_definition.previous_version.description/,
389 numberDaysRadio: byRole('radio', { name: /new_code_definition.number_days.description/ }),
390 numberDaysInput: byRole('spinbutton'),
391 referenceBranchRadio: byRole('radio', { name: /baseline.reference_branch.description/ }),
392 chooseBranchSelect: byRole('combobox', { name: 'baseline.reference_branch.choose' }),
393 specificAnalysisRadio: byRole('radio', { name: /baseline.specific_analysis.description/ }),
394 specificAnalysisWarning: byText('baseline.specific_analysis.compliance_warning.title'),
395 analysisFromSelect: byRole('combobox', { name: 'baseline.analysis_from' }),
396 analysisListItem: byRole('radio', { name: /baseline.branch_analyses.analysis_for_x/ }),
397 saveButton: byRole('button', { name: 'save' }),
398 cancelButton: byRole('button', { name: 'cancel' }),
399 branchActionsButton: (branch: string) =>
400 byRole('button', { name: `branch_list.show_actions_for_x.${branch}` }),
401 editButton: byRole('button', { name: 'edit' }),
402 resetToDefaultButton: byRole('button', { name: 'reset_to_default' }),
403 branchNCDsBanner: byText(/new_code_definition.auto_update.branch.message/),
404 dismissButton: byLabelText('alert.dismiss'),
407 async function appIsLoaded() {
408 expect(await ui.pageHeading.find()).toBeInTheDocument();
411 async function setPreviousVersionSetting() {
412 await user.click(ui.specificSettingRadio.get());
413 await user.click(ui.previousVersionRadio.get());
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);
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);
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);
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);
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);
450 async function openBranchSettingModal(branch: string) {
451 await user.click(await ui.branchActionsButton(branch).find());
452 await user.click(ui.editButton.get());
459 setNumberDaysSetting,
460 setPreviousVersionSetting,
461 setReferenceBranchSetting,
462 setBranchPreviousVersionSetting,
463 setBranchNumberOfDaysSetting,
464 setBranchReferenceToBranchSetting,
465 openBranchSettingModal,