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.

BitbucketCloud-it.tsx 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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, within } from '@testing-library/react';
  21. import userEvent from '@testing-library/user-event';
  22. import * as React from 'react';
  23. import selectEvent from 'react-select-event';
  24. import { searchForBitbucketCloudRepositories } from '../../../../api/alm-integrations';
  25. import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock';
  26. import DopTranslationServiceMock from '../../../../api/mocks/DopTranslationServiceMock';
  27. import NewCodeDefinitionServiceMock from '../../../../api/mocks/NewCodeDefinitionServiceMock';
  28. import { renderApp } from '../../../../helpers/testReactTestingUtils';
  29. import { byLabelText, byRole, byText } from '../../../../helpers/testSelector';
  30. import { Feature } from '../../../../types/features';
  31. import CreateProjectPage from '../CreateProjectPage';
  32. import { REPOSITORY_PAGE_SIZE } from '../constants';
  33. import { CreateProjectModes } from '../types';
  34. jest.mock('../../../../api/alm-integrations');
  35. jest.mock('../../../../api/alm-settings');
  36. let almIntegrationHandler: AlmIntegrationsServiceMock;
  37. let dopTranslationHandler: DopTranslationServiceMock;
  38. let newCodePeriodHandler: NewCodeDefinitionServiceMock;
  39. const ui = {
  40. cancelButton: byRole('button', { name: 'cancel' }),
  41. bitbucketCloudCreateProjectButton: byText(
  42. 'onboarding.create_project.select_method.bitbucketcloud',
  43. ),
  44. bitbucketCloudOnboardingTitle: byRole('heading', {
  45. name: 'onboarding.create_project.bitbucketcloud.title',
  46. }),
  47. monorepoSetupLink: byRole('link', {
  48. name: 'onboarding.create_project.subtitle_monorepo_setup_link',
  49. }),
  50. monorepoTitle: byRole('heading', {
  51. name: 'onboarding.create_project.monorepo.titlealm.bitbucketcloud',
  52. }),
  53. personalAccessTokenInput: byRole('textbox', {
  54. name: /onboarding.create_project.enter_pat/,
  55. }),
  56. instanceSelector: byLabelText(/alm.configuration.selector.label/),
  57. userName: byRole('textbox', {
  58. name: /onboarding\.create_project\.bitbucket_cloud\.enter_username/,
  59. }),
  60. password: byRole('textbox', {
  61. name: /onboarding\.create_project\.bitbucket_cloud\.enter_password/,
  62. }),
  63. };
  64. const original = window.location;
  65. beforeAll(() => {
  66. Object.defineProperty(window, 'location', {
  67. configurable: true,
  68. value: { replace: jest.fn() },
  69. });
  70. almIntegrationHandler = new AlmIntegrationsServiceMock();
  71. dopTranslationHandler = new DopTranslationServiceMock();
  72. newCodePeriodHandler = new NewCodeDefinitionServiceMock();
  73. });
  74. beforeEach(() => {
  75. jest.clearAllMocks();
  76. almIntegrationHandler.reset();
  77. dopTranslationHandler.reset();
  78. newCodePeriodHandler.reset();
  79. });
  80. afterAll(() => {
  81. Object.defineProperty(window, 'location', { configurable: true, value: original });
  82. });
  83. it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => {
  84. const user = userEvent.setup();
  85. renderCreateProject();
  86. expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument();
  87. expect(await ui.instanceSelector.find()).toBeInTheDocument();
  88. await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-1/]);
  89. expect(
  90. await screen.findByText('onboarding.create_project.bitbucket_cloud.enter_password'),
  91. ).toBeInTheDocument();
  92. expect(
  93. screen.getByText('onboarding.create_project.enter_password.instructions.bitbucket_cloud'),
  94. ).toBeInTheDocument();
  95. expect(
  96. screen.getByText(
  97. 'onboarding.create_project.pat.expired.info_message onboarding.create_project.pat.expired.info_message_contact',
  98. ),
  99. ).toBeInTheDocument();
  100. expect(screen.getByRole('button', { name: 'save' })).toBeDisabled();
  101. await user.click(ui.userName.get());
  102. await user.type(ui.userName.get(), 'username');
  103. expect(ui.userName.get()).toHaveValue('username');
  104. await user.click(ui.password.get());
  105. await user.type(ui.password.get(), 'password');
  106. expect(ui.password.get()).toHaveValue('password');
  107. expect(screen.getByRole('button', { name: 'save' })).toBeEnabled();
  108. await user.click(screen.getByRole('button', { name: 'save' }));
  109. expect(screen.getByText('BitbucketCloud Repo 1')).toBeInTheDocument();
  110. expect(screen.getByText('BitbucketCloud Repo 2')).toBeInTheDocument();
  111. });
  112. it('should show import project feature when PAT is already set', async () => {
  113. const user = userEvent.setup();
  114. let projectItem;
  115. renderCreateProject();
  116. expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument();
  117. expect(await ui.instanceSelector.find()).toBeInTheDocument();
  118. await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]);
  119. expect(await screen.findByText('BitbucketCloud Repo 1')).toBeInTheDocument();
  120. expect(screen.getByText('BitbucketCloud Repo 2')).toBeInTheDocument();
  121. projectItem = screen.getByRole('listitem', { name: /BitbucketCloud Repo 1/ });
  122. expect(
  123. within(projectItem).getByText('onboarding.create_project.repository_imported'),
  124. ).toBeInTheDocument();
  125. expect(
  126. within(projectItem).getByRole('link', { name: /BitbucketCloud Repo 1/ }),
  127. ).toBeInTheDocument();
  128. expect(within(projectItem).getByRole('link', { name: /BitbucketCloud Repo 1/ })).toHaveAttribute(
  129. 'href',
  130. '/dashboard?id=key',
  131. );
  132. projectItem = screen.getByRole('listitem', { name: /BitbucketCloud Repo 2/ });
  133. const setupButton = within(projectItem).getByRole('button', {
  134. name: 'onboarding.create_project.import',
  135. });
  136. await user.click(setupButton);
  137. expect(
  138. screen.getByRole('heading', { name: 'onboarding.create_x_project.new_code_definition.title1' }),
  139. ).toBeInTheDocument();
  140. await user.click(screen.getByRole('radio', { name: 'new_code_definition.global_setting' }));
  141. await user.click(
  142. screen.getByRole('button', {
  143. name: 'onboarding.create_project.new_code_definition.create_x_projects1',
  144. }),
  145. );
  146. expect(await screen.findByText('/dashboard?id=key')).toBeInTheDocument();
  147. });
  148. it('should show search filter when PAT is already set', async () => {
  149. const user = userEvent.setup();
  150. renderCreateProject();
  151. expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument();
  152. expect(await ui.instanceSelector.find()).toBeInTheDocument();
  153. await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]);
  154. await waitFor(() =>
  155. expect(searchForBitbucketCloudRepositories).toHaveBeenLastCalledWith(
  156. 'conf-bitbucketcloud-2',
  157. '',
  158. REPOSITORY_PAGE_SIZE,
  159. 1,
  160. ),
  161. );
  162. const inputSearch = screen.getByRole('searchbox', {
  163. name: 'onboarding.create_project.search_prompt',
  164. });
  165. await user.click(inputSearch);
  166. await user.type(inputSearch, 'search');
  167. await waitFor(() =>
  168. expect(searchForBitbucketCloudRepositories).toHaveBeenLastCalledWith(
  169. 'conf-bitbucketcloud-2',
  170. 'search',
  171. REPOSITORY_PAGE_SIZE,
  172. 1,
  173. ),
  174. );
  175. });
  176. it('should show no result message when there are no projects', async () => {
  177. almIntegrationHandler.setBitbucketCloudRepositories([]);
  178. renderCreateProject();
  179. expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument();
  180. expect(await ui.instanceSelector.find()).toBeInTheDocument();
  181. await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]);
  182. expect(
  183. await screen.findByText('onboarding.create_project.bitbucketcloud.no_projects'),
  184. ).toBeInTheDocument();
  185. });
  186. it('should have load more', async () => {
  187. const user = userEvent.setup();
  188. almIntegrationHandler.createRandomBitbucketCloudProjectsWithLoadMore(
  189. REPOSITORY_PAGE_SIZE,
  190. REPOSITORY_PAGE_SIZE + 1,
  191. );
  192. renderCreateProject();
  193. expect(screen.getByText('onboarding.create_project.bitbucketcloud.title')).toBeInTheDocument();
  194. expect(await ui.instanceSelector.find()).toBeInTheDocument();
  195. await selectEvent.select(ui.instanceSelector.get(), [/conf-bitbucketcloud-2/]);
  196. expect(await screen.findByRole('button', { name: 'show_more' })).toBeInTheDocument();
  197. /*
  198. * Next api call response will simulate reaching the last page so we can test the
  199. * loadmore button disapperance.
  200. */
  201. almIntegrationHandler.createRandomBitbucketCloudProjectsWithLoadMore(
  202. REPOSITORY_PAGE_SIZE + 1,
  203. REPOSITORY_PAGE_SIZE + 1,
  204. );
  205. await user.click(screen.getByRole('button', { name: 'show_more' }));
  206. await waitFor(() =>
  207. expect(searchForBitbucketCloudRepositories).toHaveBeenLastCalledWith(
  208. 'conf-bitbucketcloud-2',
  209. '',
  210. REPOSITORY_PAGE_SIZE,
  211. 2,
  212. ),
  213. );
  214. await waitFor(() => {
  215. expect(screen.queryByRole('button', { name: 'show_more' })).not.toBeInTheDocument();
  216. });
  217. });
  218. describe('Bitbucket Cloud monorepo project navigation', () => {
  219. it('should be able to access monorepo setup page from Bitbucket Cloud project import page', async () => {
  220. const user = userEvent.setup();
  221. renderCreateProject();
  222. await user.click(await ui.monorepoSetupLink.find());
  223. expect(ui.monorepoTitle.get()).toBeInTheDocument();
  224. });
  225. it('should be able to go back to Bitbucket Cloud onboarding page from monorepo setup page', async () => {
  226. const user = userEvent.setup();
  227. renderCreateProject({ isMonorepo: true });
  228. await user.click(await ui.cancelButton.find());
  229. expect(ui.bitbucketCloudOnboardingTitle.get()).toBeInTheDocument();
  230. });
  231. });
  232. function renderCreateProject({
  233. isMonorepo = false,
  234. }: {
  235. isMonorepo?: boolean;
  236. } = {}) {
  237. let queryString = `mode=${CreateProjectModes.BitbucketCloud}`;
  238. if (isMonorepo) {
  239. queryString += '&mono=true';
  240. }
  241. renderApp('projects/create', <CreateProjectPage />, {
  242. navigateTo: `projects/create?${queryString}`,
  243. featureList: [Feature.MonoRepositoryPullRequestDecoration],
  244. });
  245. }