3 * Copyright (C) 2009-2024 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.
21 import userEvent from '@testing-library/user-event';
22 import { last } from 'lodash';
23 import { byLabelText, byRole, byText } from '~sonar-aligned/helpers/testSelector';
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 { 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 expect(ui.referenceBranchRadio.query()).not.toBeInTheDocument();
71 it('renders correctly with branch support feature', async () => {
72 const { ui } = getPageObjects();
73 renderProjectNewCodeDefinitionApp({
74 featureList: [Feature.BranchSupport],
75 appState: mockAppState({ canAdmin: true }),
77 await ui.appIsLoaded();
79 expect(await ui.generalSettingRadio.find()).toBeChecked();
80 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
83 expect(ui.generalSettingsLink.get()).toBeInTheDocument();
85 expect(ui.referenceBranchRadio.get()).toBeInTheDocument();
88 it('can set previous version specific setting', async () => {
89 const { ui, user } = getPageObjects();
90 renderProjectNewCodeDefinitionApp();
91 await ui.appIsLoaded();
93 expect(await ui.previousVersionRadio.find()).toHaveClass('disabled');
94 await ui.setPreviousVersionSetting();
95 expect(ui.previousVersionRadio.get()).toBeChecked();
98 await user.click(ui.saveButton.get());
100 expect(ui.saveButton.get()).toBeDisabled();
102 // Set general setting
103 await user.click(ui.generalSettingRadio.get());
104 expect(ui.previousVersionRadio.get()).toHaveClass('disabled');
105 await user.click(ui.saveButton.get());
106 expect(ui.saveButton.get()).toBeDisabled();
109 it('can set number of days specific setting', async () => {
110 const { ui, user } = getPageObjects();
111 renderProjectNewCodeDefinitionApp();
112 await ui.appIsLoaded();
114 expect(await ui.numberDaysRadio.find()).toHaveClass('disabled');
115 await ui.setNumberDaysSetting('10');
116 expect(ui.numberDaysRadio.get()).toBeChecked();
118 // Reset to initial state
119 await user.click(ui.cancelButton.get());
120 expect(ui.generalSettingRadio.get()).toBeChecked();
121 expect(ui.numberDaysRadio.get()).toHaveClass('disabled');
124 await ui.setNumberDaysSetting('10');
125 await user.click(ui.saveButton.get());
127 expect(ui.saveButton.get()).toBeDisabled();
130 it('can set reference branch specific setting', async () => {
131 const { ui, user } = getPageObjects();
132 renderProjectNewCodeDefinitionApp({
133 featureList: [Feature.BranchSupport],
135 await ui.appIsLoaded();
137 expect(await ui.referenceBranchRadio.find()).toHaveClass('disabled');
138 await ui.setReferenceBranchSetting('main');
139 expect(ui.referenceBranchRadio.get()).toBeChecked();
142 await user.click(ui.saveButton.get());
144 expect(ui.saveButton.get()).toBeDisabled();
147 it('cannot set specific analysis setting', async () => {
148 const { ui } = getPageObjects();
149 newCodeDefinitionMock.setListBranchesNewCode([
150 mockNewCodePeriodBranch({
152 type: NewCodeDefinitionType.SpecificAnalysis,
153 value: 'analysis_id',
156 projectActivityMock.setAnalysesList([
159 date: '2018-01-11T00:00:00+0200',
162 renderProjectNewCodeDefinitionApp();
163 await ui.appIsLoaded();
165 expect(await ui.specificAnalysisRadio.find()).toBeChecked();
166 expect(await ui.baselineSpecificAnalysisDate.find()).toBeInTheDocument();
168 expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
169 expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
171 expect(ui.saveButton.get()).toBeDisabled();
174 it('renders correctly branch modal', async () => {
175 const { ui } = getPageObjects();
176 renderProjectNewCodeDefinitionApp({
177 featureList: [Feature.BranchSupport],
179 await ui.appIsLoaded();
181 await ui.openBranchSettingModal('main');
183 expect(ui.specificAnalysisRadio.query()).not.toBeInTheDocument();
186 it('can set a previous version setting for branch', async () => {
187 const { ui, user } = getPageObjects();
188 renderProjectNewCodeDefinitionApp({
189 featureList: [Feature.BranchSupport],
191 await ui.appIsLoaded();
192 await ui.setBranchPreviousVersionSetting('main');
195 byRole('table').byRole('cell', { name: 'branch_list.default_setting' }).getAll(),
197 expect(byRole('table').byText('new_code_definition.previous_version').get()).toBeInTheDocument();
199 await user.click(await ui.branchActionsButton('main').find());
201 expect(ui.resetToDefaultButton.get()).toBeInTheDocument();
202 await user.click(ui.resetToDefaultButton.get());
205 byRole('table').byRole('cell', { name: 'branch_list.default_setting' }).getAll(),
209 it('can set a number of days setting for branch', async () => {
210 const { ui } = getPageObjects();
211 renderProjectNewCodeDefinitionApp({
212 featureList: [Feature.BranchSupport],
214 await ui.appIsLoaded();
216 await ui.setBranchNumberOfDaysSetting('main', '15');
218 expect(byRole('table').byText('new_code_definition.number_days: 15').get()).toBeInTheDocument();
221 it('cannot set a specific analysis setting for branch', async () => {
222 const { ui, user } = 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 user.click(await byLabelText('branch_list.show_actions_for_x.main').find());
236 await user.click(await byRole('menuitem', { name: 'edit' }).find());
237 expect(ui.specificAnalysisRadio.get()).toBeChecked();
238 expect(ui.specificAnalysisRadio.get()).toHaveClass('disabled');
239 expect(ui.specificAnalysisWarning.get()).toBeInTheDocument();
241 expect(last(ui.saveButton.getAll())).toBeDisabled();
244 it('can set a reference branch setting for branch', async () => {
245 const { ui } = getPageObjects();
246 renderProjectNewCodeDefinitionApp({
247 featureList: [Feature.BranchSupport],
249 await ui.appIsLoaded();
251 await ui.setBranchReferenceToBranchSetting('main', 'normal-branch');
254 byRole('table').byText('baseline.reference_branch: normal-branch').get(),
255 ).toBeInTheDocument();
258 it('should display NCD banner if some branches had their NCD automatically changed', async () => {
259 const { ui } = getPageObjects();
261 newCodeDefinitionMock.setListBranchesNewCode([
263 projectKey: 'test-project:test',
264 branchKey: 'test-branch',
265 type: NewCodeDefinitionType.NumberOfDays,
268 updatedAt: 1692720953662,
271 projectKey: 'test-project:test',
273 type: NewCodeDefinitionType.NumberOfDays,
275 previousNonCompliantValue: '150',
276 updatedAt: 1692721852743,
280 renderProjectNewCodeDefinitionApp({
281 featureList: [Feature.BranchSupport],
284 expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
286 ui.branchNCDsBanner.byText('new_code_definition.auto_update.branch.list_itemmaster32150').get(),
287 ).toBeInTheDocument();
290 it('should not display NCD banner if some branches had their NCD automatically changed and banne has been dismissed', async () => {
291 const { ui } = getPageObjects();
293 newCodeDefinitionMock.setListBranchesNewCode([
295 projectKey: 'test-project:test',
296 branchKey: 'test-branch',
297 type: NewCodeDefinitionType.NumberOfDays,
300 updatedAt: 1692720953662,
303 projectKey: 'test-project:test',
305 type: NewCodeDefinitionType.NumberOfDays,
307 previousNonCompliantValue: '150',
308 updatedAt: 1692721852743,
311 messagesMock.setMessageDismissed({
312 projectKey: 'test-project:test',
313 messageType: MessageTypes.BranchNcd90,
316 renderProjectNewCodeDefinitionApp({
317 featureList: [Feature.BranchSupport],
320 expect(await ui.branchNCDsBanner.query()).not.toBeInTheDocument();
323 it('should correctly dismiss branch banner', 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,
345 renderProjectNewCodeDefinitionApp({
346 featureList: [Feature.BranchSupport],
349 expect(await ui.branchNCDsBanner.find()).toBeInTheDocument();
351 const user = userEvent.setup();
352 await user.click(ui.dismissButton.get());
354 expect(ui.branchNCDsBanner.query()).not.toBeInTheDocument();
357 function renderProjectNewCodeDefinitionApp(context: RenderContext = {}, params?: string) {
358 return renderAppWithComponentContext(
363 navigateTo: params ? `baseline?id=my-project&${params}` : 'baseline?id=my-project',
366 component: mockComponent(),
371 function getPageObjects() {
372 const user = userEvent.setup();
375 pageHeading: byRole('heading', { name: 'project_baseline.page' }),
376 branchTableHeading: byText('branch_list.branch'),
377 generalSettingsLink: byRole('link', { name: 'project_baseline.page.description2.link' }),
378 generalSettingRadio: byRole('radio', { name: 'project_baseline.global_setting' }),
379 specificSettingRadio: byRole('radio', { name: 'project_baseline.specific_setting' }),
380 previousVersionRadio: byRole('radio', {
381 name: /new_code_definition.previous_version.description/,
383 numberDaysRadio: byRole('radio', { name: /new_code_definition.number_days.description/ }),
384 numberDaysInput: byRole('spinbutton'),
385 referenceBranchRadio: byRole('radio', { name: /baseline.reference_branch.description/ }),
386 chooseBranchSelect: byRole('combobox', { name: 'baseline.reference_branch.choose' }),
387 specificAnalysisRadio: byRole('radio', { name: /baseline.specific_analysis.description/ }),
388 specificAnalysisWarning: byText('baseline.specific_analysis.compliance_warning.title'),
389 saveButton: byRole('button', { name: 'save' }),
390 cancelButton: byRole('button', { name: 'cancel' }),
391 branchActionsButton: (name: string) =>
392 byRole('button', { name: `branch_list.show_actions_for_x.${name}` }),
393 resetToDefaultButton: byRole('menuitem', { name: 'reset_to_default' }),
394 branchNCDsBanner: byText(/new_code_definition.auto_update.branch.message/),
395 dismissButton: byLabelText('dismiss'),
396 baselineSpecificAnalysisDate: byText(/January 10, 2018/),
399 async function appIsLoaded() {
400 expect(await ui.pageHeading.find()).toBeInTheDocument();
403 async function setPreviousVersionSetting() {
404 await user.click(ui.specificSettingRadio.get());
405 await user.click(ui.previousVersionRadio.get());
408 async function setBranchPreviousVersionSetting(branch: string) {
409 await openBranchSettingModal(branch);
410 await user.click(last(ui.previousVersionRadio.getAll()) as HTMLElement);
411 await user.click(last(ui.saveButton.getAll()) as HTMLElement);
414 async function setNumberDaysSetting(value: string) {
415 await user.click(ui.specificSettingRadio.get());
416 await user.click(ui.numberDaysRadio.get());
417 await user.clear(ui.numberDaysInput.get());
418 await user.type(ui.numberDaysInput.get(), value);
421 async function setBranchNumberOfDaysSetting(branch: string, value: string) {
422 await openBranchSettingModal(branch);
423 await user.click(last(ui.numberDaysRadio.getAll()) as HTMLElement);
424 await user.clear(ui.numberDaysInput.get());
425 await user.type(ui.numberDaysInput.get(), value);
426 await user.click(last(ui.saveButton.getAll()) as HTMLElement);
429 async function setReferenceBranchSetting(branch: string) {
430 await user.click(ui.specificSettingRadio.get());
431 await user.click(ui.referenceBranchRadio.get());
433 await user.click(ui.chooseBranchSelect.get());
434 await user.click(byRole('option', { name: new RegExp(branch) }).get());
437 async function setBranchReferenceToBranchSetting(branch: string, branchRef: string) {
438 await openBranchSettingModal(branch);
439 await user.click(last(ui.referenceBranchRadio.getAll()) as HTMLElement);
441 await user.click(ui.chooseBranchSelect.get());
442 await user.click(byRole('option', { name: new RegExp(branchRef) }).get());
444 await user.click(last(ui.saveButton.getAll()) as HTMLElement);
447 async function openBranchSettingModal(branch: string) {
448 await user.click(await byLabelText(`branch_list.edit_for_x.${branch}`).find());
455 setNumberDaysSetting,
456 setPreviousVersionSetting,
457 setReferenceBranchSetting,
458 setBranchPreviousVersionSetting,
459 setBranchNumberOfDaysSetting,
460 setBranchReferenceToBranchSetting,
461 openBranchSettingModal,