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 } 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';
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';
42 jest.mock('../../../../api/newCodeDefinition');
43 jest.mock('../../../../api/projectActivity');
44 jest.mock('../../../../api/branches');
46 const newCodeDefinitionMock = new NewCodeDefinitionServiceMock();
47 const projectActivityMock = new ProjectActivityServiceMock();
48 const branchHandler = new BranchesServiceMock();
49 const messagesMock = new MessagesServiceMock();
52 branchHandler.reset();
53 newCodeDefinitionMock.reset();
54 projectActivityMock.reset();
58 it('renders correctly without branch support feature', async () => {
59 const { ui } = getPageObjects();
60 renderProjectNewCodeDefinitionApp();
61 await ui.appIsLoaded();
63 expect(await ui.generalSettingRadio.find()).toBeChecked();
64 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
67 expect(ui.generalSettingsLink.query()).not.toBeInTheDocument();
69 // Specific branch setting is not rendered without feature branch
70 expect(ui.branchListHeading.query()).not.toBeInTheDocument();
71 expect(ui.referenceBranchRadio.query()).not.toBeInTheDocument();
74 it('renders correctly with branch support feature', async () => {
75 const { ui } = getPageObjects();
76 renderProjectNewCodeDefinitionApp({
77 featureList: [Feature.BranchSupport],
78 appState: mockAppState({ canAdmin: true }),
80 await ui.appIsLoaded();
82 expect(await ui.generalSettingRadio.find()).toBeChecked();
83 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
86 expect(ui.generalSettingsLink.get()).toBeInTheDocument();
88 // Specific branch setting is rendered with feature support branch
89 expect(ui.branchListHeading.get()).toBeInTheDocument();
90 expect(ui.referenceBranchRadio.get()).toBeInTheDocument();
93 it('can set previous version specific setting', async () => {
94 const { ui, user } = getPageObjects();
95 renderProjectNewCodeDefinitionApp();
96 await ui.appIsLoaded();
98 expect(await ui.previousVersionRadio.find()).toHaveClass('disabled');
99 await ui.setPreviousVersionSetting();
100 expect(ui.previousVersionRadio.get()).toBeChecked();
103 await user.click(ui.saveButton.get());
105 expect(ui.saveButton.get()).toBeDisabled();
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();
114 it('can set number of days specific setting', async () => {
115 const { ui, user } = getPageObjects();
116 renderProjectNewCodeDefinitionApp();
117 await ui.appIsLoaded();
119 expect(await ui.numberDaysRadio.find()).toHaveClass('disabled');
120 await ui.setNumberDaysSetting('10');
121 expect(ui.numberDaysRadio.get()).toBeChecked();
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');
129 await ui.setNumberDaysSetting('10');
130 await user.click(ui.saveButton.get());
132 expect(ui.saveButton.get()).toBeDisabled();
135 it('can set reference branch specific setting', async () => {
136 const { ui, user } = getPageObjects();
137 renderProjectNewCodeDefinitionApp({
138 featureList: [Feature.BranchSupport],
140 await ui.appIsLoaded();
142 expect(await ui.referenceBranchRadio.find()).toHaveClass('disabled');
143 await ui.setReferenceBranchSetting('main');
144 expect(ui.referenceBranchRadio.get()).toBeChecked();
147 await user.click(ui.saveButton.get());
149 expect(ui.saveButton.get()).toBeDisabled();
152 it('cannot set specific analysis setting', async () => {
153 const { ui } = getPageObjects();
154 newCodeDefinitionMock.setListBranchesNewCode([
155 mockNewCodePeriodBranch({
157 type: NewCodeDefinitionType.SpecificAnalysis,
158 value: 'analysis_id',
161 projectActivityMock.setAnalysesList([
164 date: '2018-01-11T00:00:00+0200',
167 renderProjectNewCodeDefinitionApp();
168 await ui.appIsLoaded();
170 expect(await ui.specificAnalysisRadio.find()).toBeChecked();
171 expect(ui.baselineSpecificAnalysisDate.get()).toBeInTheDocument();
173 expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
174 expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
176 expect(ui.saveButton.get()).toBeDisabled();
179 it('renders correctly branch modal', async () => {
180 const { ui } = getPageObjects();
181 renderProjectNewCodeDefinitionApp({
182 featureList: [Feature.BranchSupport],
184 await ui.appIsLoaded();
186 await ui.openBranchSettingModal('main');
188 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
191 it('can set a previous version setting for branch', async () => {
192 const { ui, user } = getPageObjects();
193 renderProjectNewCodeDefinitionApp({
194 featureList: [Feature.BranchSupport],
196 await ui.appIsLoaded();
197 await ui.setBranchPreviousVersionSetting('main');
200 byRole('table').byRole('cell', { name: 'branch_list.default_setting' }).getAll(),
202 expect(byRole('table').byText('new_code_definition.previous_version').get()).toBeInTheDocument();
204 await user.click(await ui.branchActionsButton('main').find());
206 expect(ui.resetToDefaultButton.get()).toBeInTheDocument();
207 await user.click(ui.resetToDefaultButton.get());
210 byRole('table').byRole('cell', { name: 'branch_list.default_setting' }).getAll(),
214 it('can set a number of days setting for branch', async () => {
215 const { ui } = getPageObjects();
216 renderProjectNewCodeDefinitionApp({
217 featureList: [Feature.BranchSupport],
219 await ui.appIsLoaded();
221 await ui.setBranchNumberOfDaysSetting('main', '15');
223 expect(byRole('table').byText('new_code_definition.number_days: 15').get()).toBeInTheDocument();
226 it('cannot set a specific analysis setting for branch', async () => {
227 const { ui, user } = getPageObjects();
228 newCodeDefinitionMock.setListBranchesNewCode([
229 mockNewCodePeriodBranch({
231 type: NewCodeDefinitionType.SpecificAnalysis,
232 value: 'analysis_id',
235 renderProjectNewCodeDefinitionApp({
236 featureList: [Feature.BranchSupport],
238 await ui.appIsLoaded();
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();
246 expect(last(ui.saveButton.getAll())).toBeDisabled();
249 it('can set a reference branch setting for branch', async () => {
250 const { ui } = getPageObjects();
251 renderProjectNewCodeDefinitionApp({
252 featureList: [Feature.BranchSupport],
254 await ui.appIsLoaded();
256 await ui.setBranchReferenceToBranchSetting('main', 'normal-branch');
259 byRole('table').byText('baseline.reference_branch: normal-branch').get(),
260 ).toBeInTheDocument();
263 it('should display NCD banner if some branches had their NCD automatically changed', async () => {
264 const { ui } = getPageObjects();
266 newCodeDefinitionMock.setListBranchesNewCode([
268 projectKey: 'test-project:test',
269 branchKey: 'test-branch',
270 type: NewCodeDefinitionType.NumberOfDays,
273 updatedAt: 1692720953662,
276 projectKey: 'test-project:test',
278 type: NewCodeDefinitionType.NumberOfDays,
280 previousNonCompliantValue: '150',
281 updatedAt: 1692721852743,
285 renderProjectNewCodeDefinitionApp({
286 featureList: [Feature.BranchSupport],
289 expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
291 ui.branchNCDsBanner.byText('new_code_definition.auto_update.branch.list_itemmaster32150').get(),
292 ).toBeInTheDocument();
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();
298 newCodeDefinitionMock.setListBranchesNewCode([
300 projectKey: 'test-project:test',
301 branchKey: 'test-branch',
302 type: NewCodeDefinitionType.NumberOfDays,
305 updatedAt: 1692720953662,
308 projectKey: 'test-project:test',
310 type: NewCodeDefinitionType.NumberOfDays,
312 previousNonCompliantValue: '150',
313 updatedAt: 1692721852743,
316 messagesMock.setMessageDismissed({
317 projectKey: 'test-project:test',
318 messageType: MessageTypes.BranchNcd90,
321 renderProjectNewCodeDefinitionApp({
322 featureList: [Feature.BranchSupport],
325 expect(await ui.branchNCDsBanner.query()).not.toBeInTheDocument();
328 it('should correctly dismiss branch banner', async () => {
329 const { ui } = getPageObjects();
331 newCodeDefinitionMock.setListBranchesNewCode([
333 projectKey: 'test-project:test',
334 branchKey: 'test-branch',
335 type: NewCodeDefinitionType.NumberOfDays,
338 updatedAt: 1692720953662,
341 projectKey: 'test-project:test',
343 type: NewCodeDefinitionType.NumberOfDays,
345 previousNonCompliantValue: '150',
346 updatedAt: 1692721852743,
350 renderProjectNewCodeDefinitionApp({
351 featureList: [Feature.BranchSupport],
354 expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
356 const user = userEvent.setup();
357 await act(async () => {
358 await user.click(ui.dismissButton.get());
361 expect(ui.branchNCDsBanner.query()).not.toBeInTheDocument();
364 function renderProjectNewCodeDefinitionApp(context: RenderContext = {}, params?: string) {
365 return renderAppWithComponentContext(
370 navigateTo: params ? `baseline?id=my-project&${params}` : 'baseline?id=my-project',
373 component: mockComponent(),
378 function getPageObjects() {
379 const user = userEvent.setup();
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/,
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/),
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 byLabelText(`branch_list.edit_for_x.${branch}`).find());
458 setNumberDaysSetting,
459 setPreviousVersionSetting,
460 setReferenceBranchSetting,
461 setBranchPreviousVersionSetting,
462 setBranchNumberOfDaysSetting,
463 setBranchReferenceToBranchSetting,
464 openBranchSettingModal,