You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TutorialSelection-it.tsx 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  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.
  10. *
  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.
  15. *
  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.
  19. */
  20. import { screen, waitFor } 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 { getScannableProjects } from '../../../api/components';
  25. import AlmSettingsServiceMock from '../../../api/mocks/AlmSettingsServiceMock';
  26. import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock';
  27. import UserTokensMock from '../../../api/mocks/UserTokensMock';
  28. import { mockComponent } from '../../../helpers/mocks/component';
  29. import { mockLoggedInUser } from '../../../helpers/testMocks';
  30. import { renderApp } from '../../../helpers/testReactTestingUtils';
  31. import { byRole, byText } from '../../../helpers/testSelector';
  32. import { AlmKeys } from '../../../types/alm-settings';
  33. import { Feature } from '../../../types/features';
  34. import { Permissions } from '../../../types/permissions';
  35. import { SettingsKey } from '../../../types/settings';
  36. import TutorialSelection, { TutorialSelectionProps } from '../TutorialSelection';
  37. import { TutorialModes } from '../types';
  38. jest.mock('../../../api/branches');
  39. jest.mock('../../../helpers/urls', () => ({
  40. ...jest.requireActual('../../../helpers/urls'),
  41. getHostUrl: jest.fn().mockReturnValue('http://host.url'),
  42. }));
  43. jest.mock('../../../api/components', () => ({
  44. getScannableProjects: jest.fn().mockResolvedValue({ projects: [] }),
  45. }));
  46. let settingsMock: SettingsServiceMock;
  47. let tokenMock: UserTokensMock;
  48. let almMock: AlmSettingsServiceMock;
  49. beforeAll(() => {
  50. settingsMock = new SettingsServiceMock();
  51. tokenMock = new UserTokensMock();
  52. almMock = new AlmSettingsServiceMock();
  53. });
  54. afterEach(() => {
  55. tokenMock.reset();
  56. settingsMock.reset();
  57. almMock.reset();
  58. });
  59. beforeEach(() => {
  60. jest.clearAllMocks();
  61. });
  62. const ui = {
  63. loading: byText('loading'),
  64. noScanRights: byText('onboarding.tutorial.no_scan_rights'),
  65. monoRepoSecretInfo: byText('onboarding.tutorial.with.github_action.create_secret.monorepo_info'),
  66. monoRepoYamlDocLink: byRole('link', {
  67. name: 'onboarding.tutorial.with.github_action.monorepo.see_yaml_instructions',
  68. }),
  69. chooseTutorialLink: (mode: TutorialModes) =>
  70. byRole('link', { name: `onboarding.tutorial.choose_method.${mode}` }),
  71. chooseBootstrapper: (bootstrapper: string) =>
  72. byRole('radio', { name: `onboarding.build.${bootstrapper}` }),
  73. };
  74. it.each([
  75. [TutorialModes.Jenkins, 'onboarding.tutorial.with.jenkins.title'],
  76. [TutorialModes.AzurePipelines, 'onboarding.tutorial.with.azure_pipelines.title'],
  77. [
  78. TutorialModes.BitbucketPipelines,
  79. 'onboarding.tutorial.with.bitbucket_pipelines.variables.title',
  80. ],
  81. [TutorialModes.GitHubActions, 'onboarding.tutorial.with.github_action.create_secret.title'],
  82. [TutorialModes.GitLabCI, 'onboarding.tutorial.with.gitlab_ci.title'],
  83. [TutorialModes.Local, 'onboarding.project_analysis.header'],
  84. [TutorialModes.OtherCI, 'onboarding.project_analysis.header'],
  85. ])('should properly click link for %s', async (mode, title) => {
  86. const user = userEvent.setup();
  87. const breadcrumbs = `onboarding.tutorial.breadcrumbs.${mode}`;
  88. renderTutorialSelection({});
  89. await waitOnDataLoaded();
  90. expect(screen.getByText('onboarding.tutorial.choose_method')).toBeInTheDocument();
  91. expect(screen.queryByText(breadcrumbs)).not.toBeInTheDocument();
  92. await user.click(ui.chooseTutorialLink(mode).get());
  93. expect(screen.getByText(title)).toBeInTheDocument();
  94. expect(screen.getByText(breadcrumbs)).toBeInTheDocument();
  95. });
  96. it('should properly detect and render GitHub monorepo-specific instructions for GitHub Actions', async () => {
  97. almMock.handleSetProjectBinding(AlmKeys.GitHub, {
  98. project: 'foo',
  99. almSetting: 'foo',
  100. repository: 'repo',
  101. monorepo: true,
  102. });
  103. const user = userEvent.setup();
  104. renderTutorialSelection({});
  105. await waitOnDataLoaded();
  106. await user.click(ui.chooseTutorialLink(TutorialModes.GitHubActions).get());
  107. expect(ui.monoRepoSecretInfo.get()).toBeInTheDocument();
  108. expect(ui.monoRepoYamlDocLink.query()).not.toBeInTheDocument();
  109. await user.click(ui.chooseBootstrapper('maven').get());
  110. expect(ui.monoRepoYamlDocLink.get()).toBeInTheDocument();
  111. await user.click(ui.chooseBootstrapper('gradle').get());
  112. expect(ui.monoRepoYamlDocLink.get()).toBeInTheDocument();
  113. await user.click(ui.chooseBootstrapper('dotnet').get());
  114. expect(ui.monoRepoYamlDocLink.get()).toBeInTheDocument();
  115. await user.click(ui.chooseBootstrapper('other').get());
  116. expect(ui.monoRepoYamlDocLink.get()).toBeInTheDocument();
  117. });
  118. it('should properly render GitHub project tutorials for GitHub Actions', async () => {
  119. almMock.handleSetProjectBinding(AlmKeys.GitHub, {
  120. project: 'foo',
  121. almSetting: 'foo',
  122. repository: 'repo',
  123. monorepo: false,
  124. });
  125. const user = userEvent.setup();
  126. renderTutorialSelection({});
  127. await waitOnDataLoaded();
  128. await user.click(ui.chooseTutorialLink(TutorialModes.GitHubActions).get());
  129. expect(ui.monoRepoSecretInfo.query()).not.toBeInTheDocument();
  130. await user.click(ui.chooseBootstrapper('maven').get());
  131. expect(ui.monoRepoYamlDocLink.query()).not.toBeInTheDocument();
  132. });
  133. it.each([
  134. [
  135. AlmKeys.GitHub,
  136. [TutorialModes.GitHubActions, TutorialModes.Jenkins, TutorialModes.AzurePipelines],
  137. ],
  138. [AlmKeys.GitLab, [TutorialModes.GitLabCI, TutorialModes.Jenkins]],
  139. [AlmKeys.Azure, [TutorialModes.AzurePipelines]],
  140. [AlmKeys.BitbucketServer, [TutorialModes.Jenkins]],
  141. [AlmKeys.BitbucketCloud, [TutorialModes.BitbucketPipelines, TutorialModes.Jenkins]],
  142. ])('should show correct buttons if project is bound to %s', async (alm, modes) => {
  143. almMock.handleSetProjectBinding(alm, {
  144. project: 'foo',
  145. almSetting: 'foo',
  146. repository: 'repo',
  147. monorepo: false,
  148. });
  149. renderTutorialSelection();
  150. await waitOnDataLoaded();
  151. modes.forEach((mode) => expect(ui.chooseTutorialLink(mode).get()).toBeInTheDocument());
  152. });
  153. it('should correctly fetch the corresponding ALM setting', async () => {
  154. almMock.handleSetProjectBinding(AlmKeys.GitHub, {
  155. project: 'foo',
  156. almSetting: 'conf-github-1',
  157. repository: 'repo',
  158. monorepo: false,
  159. });
  160. renderTutorialSelection({}, `tutorials?selectedTutorial=${TutorialModes.Jenkins}&id=foo`);
  161. await waitOnDataLoaded();
  162. expect(await screen.findByText('http://url', { exact: false })).toBeInTheDocument();
  163. });
  164. it('should correctly fetch the instance URL', async () => {
  165. settingsMock.set(SettingsKey.ServerBaseUrl, 'http://sq.example.com');
  166. const user = userEvent.setup();
  167. renderTutorialSelection();
  168. await waitOnDataLoaded();
  169. await startLocalTutorial(user);
  170. expect(
  171. screen.getByText('-Dsonar.host.url=http://sq.example.com', { exact: false }),
  172. ).toBeInTheDocument();
  173. });
  174. it('should fallback on the host URL', async () => {
  175. const user = userEvent.setup();
  176. renderTutorialSelection();
  177. await waitOnDataLoaded();
  178. await startLocalTutorial(user);
  179. expect(
  180. screen.getByText('-Dsonar.host.url=http://host.url', { exact: false }),
  181. ).toBeInTheDocument();
  182. });
  183. it('should not display a warning if the user has no global scan permission, but can scan the project', async () => {
  184. jest
  185. .mocked(getScannableProjects)
  186. .mockResolvedValueOnce({ projects: [{ key: 'foo', name: 'foo' }] });
  187. renderTutorialSelection({ currentUser: mockLoggedInUser() });
  188. await waitOnDataLoaded();
  189. expect(ui.noScanRights.query()).not.toBeInTheDocument();
  190. });
  191. it('should correctly display a warning if the user has no scan permissions', async () => {
  192. renderTutorialSelection({ currentUser: mockLoggedInUser() });
  193. await waitOnDataLoaded();
  194. expect(ui.noScanRights.query()).toBeInTheDocument();
  195. });
  196. async function waitOnDataLoaded() {
  197. await waitFor(() => {
  198. expect(ui.loading.query()).not.toBeInTheDocument();
  199. });
  200. }
  201. async function startLocalTutorial(user: UserEvent) {
  202. await user.click(ui.chooseTutorialLink(TutorialModes.Local).get());
  203. await user.click(screen.getByRole('button', { name: 'onboarding.token.generate' }));
  204. await user.click(screen.getByRole('button', { name: 'continue' }));
  205. await user.click(screen.getByRole('radio', { name: 'onboarding.build.maven' }));
  206. }
  207. function renderTutorialSelection(
  208. props: Partial<TutorialSelectionProps> = {},
  209. navigateTo: string = 'tutorials?id=bar',
  210. ) {
  211. return renderApp(
  212. '/tutorials',
  213. <TutorialSelection
  214. component={mockComponent({ key: 'foo' })}
  215. currentUser={mockLoggedInUser({ permissions: { global: [Permissions.Scan] } })}
  216. {...props}
  217. />,
  218. { featureList: [Feature.BranchSupport], navigateTo },
  219. );
  220. }