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 { screen, within } from '@testing-library/react';
21 import userEvent from '@testing-library/user-event';
22 import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
23 import * as React from 'react';
24 import UserTokensMock from '../../../../api/mocks/UserTokensMock';
25 import { mockComponent } from '../../../../helpers/mocks/component';
26 import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks';
27 import { renderApp, RenderContext } from '../../../../helpers/testReactTestingUtils';
28 import { Permissions } from '../../../../types/permissions';
29 import { TokenType } from '../../../../types/token';
30 import { OSs } from '../../types';
31 import AzurePipelinesTutorial, { AzurePipelinesTutorialProps } from '../AzurePipelinesTutorial';
33 jest.mock('../../../../api/user-tokens');
35 jest.mock('../../../../api/settings', () => ({
36 getAllValues: jest.fn().mockResolvedValue([]),
39 let tokenMock: UserTokensMock;
42 tokenMock = new UserTokensMock();
49 it('should render correctly and allow navigating between the different steps', async () => {
50 renderAzurePipelinesTutorial();
51 const user = userEvent.setup();
54 screen.getByRole('heading', { name: 'onboarding.tutorial.with.azure_pipelines.title' })
55 ).toBeInTheDocument();
58 assertDefaultStepIsCorrectlyRendered();
61 await goToNextStep(user);
64 assertServiceEndpointStepIsCorrectlyRendered();
67 await clickButton(user, 'onboarding.token.generate.long');
68 const modal = screen.getByRole('dialog');
69 await clickButton(user, 'onboarding.token.generate', modal);
70 const lastToken = tokenMock.getLastToken();
71 if (lastToken === undefined) {
72 throw new Error("Couldn't find the latest generated token.");
74 expect(lastToken.type).toBe(TokenType.Global);
75 expect(within(modal).getByRole('alert')).toHaveTextContent(
76 `users.tokens.new_token_created.${lastToken.token}`
78 await clickButton(user, 'continue', modal);
81 await goToNextStep(user);
83 //// Analysis step: .NET
84 await clickButton(user, 'onboarding.build.dotnet');
85 assertDotNetStepIsCorrectlyRendered();
87 //// Analysis step: Maven
88 await clickButton(user, 'onboarding.build.maven');
89 assertMavenStepIsCorrectlyRendered();
91 //// Analysis step: Gradle
92 await clickButton(user, 'onboarding.build.gradle');
93 assertGradleStepIsCorrectlyRendered();
95 //// Analysis step: C Family
96 await clickButton(user, 'onboarding.build.cfamily');
99 await clickButton(user, `onboarding.build.other.os.${OSs.Linux}`);
100 assertCFamilyStepIsCorrectlyRendered(OSs.Linux);
102 await clickButton(user, `onboarding.build.other.os.${OSs.Windows}`);
103 assertCFamilyStepIsCorrectlyRendered(OSs.Windows);
105 await clickButton(user, `onboarding.build.other.os.${OSs.MacOS}`);
106 assertCFamilyStepIsCorrectlyRendered(OSs.MacOS);
108 //// Analysis step: Other
109 await clickButton(user, 'onboarding.build.other');
110 assertOtherStepIsCorrectlyRendered();
113 await clickButton(user, 'tutorials.finish');
114 assertFinishStepIsCorrectlyRendered();
117 it('allows to navigate back to a previous step', async () => {
118 renderAzurePipelinesTutorial();
119 const user = userEvent.setup();
121 // No clickable steps.
123 screen.queryByRole('button', {
124 name: '1 onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
126 ).not.toBeInTheDocument();
128 // Go to the next steps.
129 await goToNextStep(user);
130 await goToNextStep(user);
132 // The first 2 steps become clickable.
134 screen.getByRole('button', {
135 name: '1 onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
137 ).toBeInTheDocument();
139 screen.getByRole('button', {
140 name: '2 onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title',
142 ).toBeInTheDocument();
144 // Navigate back to the first step.
145 await clickButton(user, '1 onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title');
147 // No more clickable steps.
149 screen.queryByRole('button', {
150 name: '1 onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
152 ).not.toBeInTheDocument();
155 it('should not offer CFamily analysis if the language is not available', async () => {
156 renderAzurePipelinesTutorial(undefined, { languages: {} });
157 const user = userEvent.setup();
159 // Go to the analysis step.
160 await goToNextStep(user);
161 await goToNextStep(user);
163 expect(screen.getByRole('button', { name: 'onboarding.build.dotnet' })).toBeInTheDocument();
165 screen.queryByRole('button', { name: 'onboarding.build.cfamily' })
166 ).not.toBeInTheDocument();
169 function assertDefaultStepIsCorrectlyRendered() {
171 screen.getByRole('heading', {
172 name: 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
174 ).toBeInTheDocument();
177 function assertServiceEndpointStepIsCorrectlyRendered() {
179 screen.getByRole('heading', {
180 name: 'onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title',
182 ).toBeInTheDocument();
183 expect(getCopyToClipboardValue()).toBe('https://sonarqube.example.com/');
185 screen.getByRole('button', { name: 'onboarding.token.generate.long' })
186 ).toBeInTheDocument();
189 function assertDotNetStepIsCorrectlyRendered() {
191 screen.getByRole('heading', {
192 name: 'onboarding.tutorial.with.azure_pipelines.BranchAnalysis.title',
194 ).toBeInTheDocument();
195 expect(getCopyToClipboardValue()).toBe('foo');
198 function assertMavenStepIsCorrectlyRendered() {
199 expect(getCopyToClipboardValue()).toMatchSnapshot('maven, copy additional properties');
202 function assertGradleStepIsCorrectlyRendered() {
203 expect(getCopyToClipboardValue()).toMatchSnapshot('gradle, copy additional properties');
206 function assertCFamilyStepIsCorrectlyRendered(os: string) {
207 expect(getCopyToClipboardValue(0)).toMatchSnapshot(`cfamily ${os}, copy shell script`);
208 expect(getCopyToClipboardValue(1)).toBe('foo');
209 expect(getCopyToClipboardValue(2)).toMatchSnapshot(`cfamily ${os}, copy additional properties`);
210 expect(getCopyToClipboardValue(3)).toMatchSnapshot(`cfamily ${os}, copy build-wrapper command`);
213 function assertOtherStepIsCorrectlyRendered() {
214 expect(getCopyToClipboardValue()).toBe('foo');
217 function assertFinishStepIsCorrectlyRendered() {
219 screen.getByRole('heading', {
220 name: 'onboarding.tutorial.ci_outro.all_set.title',
222 ).toBeInTheDocument();
225 function renderAzurePipelinesTutorial(
226 props: Partial<AzurePipelinesTutorialProps> = {},
227 { languages = { c: mockLanguage({ key: 'c' }) } }: RenderContext = {}
231 <AzurePipelinesTutorial
232 baseUrl="https://sonarqube.example.com/"
233 component={mockComponent({ key: 'foo' })}
234 currentUser={mockLoggedInUser({ permissions: { global: [Permissions.Scan] } })}
235 willRefreshAutomatically={true}
242 async function clickButton(user: UserEvent, name: string, context?: HTMLElement) {
244 await user.click(within(context).getByRole('button', { name }));
246 await user.click(screen.getByRole('button', { name }));
250 async function goToNextStep(user: UserEvent) {
251 await clickButton(user, 'continue');
254 function getCopyToClipboardValue(i = 0, name = 'copy_to_clipboard') {
255 return screen.getAllByRole('button', { name })[i].getAttribute('data-clipboard-text');