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.

Manual-it.tsx 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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 { 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 AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock';
  24. import DopTranslationServiceMock from '../../../../api/mocks/DopTranslationServiceMock';
  25. import NewCodeDefinitionServiceMock from '../../../../api/mocks/NewCodeDefinitionServiceMock';
  26. import { ProjectsServiceMock } from '../../../../api/mocks/ProjectsServiceMock';
  27. import { getNewCodeDefinition } from '../../../../api/newCodeDefinition';
  28. import { mockProject } from '../../../../helpers/mocks/projects';
  29. import { mockAppState, mockCurrentUser } from '../../../../helpers/testMocks';
  30. import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
  31. import { byRole, byText } from '../../../../helpers/testSelector';
  32. import { NewCodeDefinitionType } from '../../../../types/new-code-definition';
  33. import { Permissions } from '../../../../types/permissions';
  34. import routes from '../../../projects/routes';
  35. jest.mock('../../../../api/measures');
  36. jest.mock('../../../../api/favorites');
  37. jest.mock('../../../../api/alm-settings');
  38. jest.mock('../../../../api/dop-translation');
  39. jest.mock('../../../../api/newCodeDefinition');
  40. jest.mock('../../../../api/project-management', () => ({
  41. createProject: jest.fn().mockReturnValue(Promise.resolve({ project: mockProject() })),
  42. }));
  43. jest.mock('../../../../api/components', () => ({
  44. ...jest.requireActual('../../../../api/components'),
  45. searchProjects: jest.fn(),
  46. getScannableProjects: jest.fn(),
  47. doesComponentExists: jest
  48. .fn()
  49. .mockImplementation(({ component }) => Promise.resolve(component === 'exists')),
  50. }));
  51. jest.mock('../../../../api/settings', () => ({
  52. getValue: jest.fn().mockResolvedValue({ value: 'main' }),
  53. }));
  54. const ui = {
  55. manualCreateProjectOption: byText('onboarding.create_project.select_method.manual'),
  56. manualProjectHeader: byText('onboarding.create_project.manual.title'),
  57. displayNameField: byRole('textbox', {
  58. name: /onboarding.create_project.display_name/,
  59. }),
  60. projectNextButton: byRole('button', { name: 'next' }),
  61. newCodeDefinitionSection: byRole('region', {
  62. name: 'onboarding.create_project.new_code_definition.title',
  63. }),
  64. newCodeDefinitionHeader: byText('onboarding.create_x_project.new_code_definition.title1'),
  65. inheritGlobalNcdRadio: byRole('radio', { name: 'new_code_definition.global_setting' }),
  66. projectCreateButton: byRole('button', {
  67. name: 'onboarding.create_project.new_code_definition.create_x_projects1',
  68. }),
  69. cancelButton: byRole('button', { name: 'cancel' }),
  70. closeButton: byRole('button', { name: 'clear' }),
  71. createProjectsButton: byRole('button', { name: 'projects.add' }),
  72. createLocalProject: byRole('menuitem', { name: 'my_account.add_project.manual' }),
  73. overrideNcdRadio: byRole('radio', { name: 'new_code_definition.specific_setting' }),
  74. ncdOptionPreviousVersionRadio: byRole('radio', {
  75. name: /new_code_definition.previous_version/,
  76. }),
  77. ncdOptionRefBranchRadio: byRole('radio', {
  78. name: /new_code_definition.reference_branch/,
  79. }),
  80. ncdOptionDaysRadio: byRole('radio', {
  81. name: /new_code_definition.number_days/,
  82. }),
  83. ncdOptionDaysInput: byRole('spinbutton', {
  84. name: /new_code_definition.number_days.specify_days/,
  85. }),
  86. ncdOptionDaysInputError: byText('new_code_definition.number_days.invalid.1.90'),
  87. projectDashboardText: byText('/dashboard?id=foo'),
  88. projectsPageTitle: byRole('heading', { name: 'projects.page' }),
  89. };
  90. async function fillFormAndNext(displayName: string, user: UserEvent) {
  91. expect(ui.manualProjectHeader.get()).toBeInTheDocument();
  92. await user.click(ui.displayNameField.get());
  93. await user.keyboard(displayName);
  94. expect(ui.projectNextButton.get()).toBeEnabled();
  95. await user.click(ui.projectNextButton.get());
  96. }
  97. let almSettingsHandler: AlmSettingsServiceMock;
  98. let dopTranslationHandler: DopTranslationServiceMock;
  99. let newCodePeriodHandler: NewCodeDefinitionServiceMock;
  100. let projectHandler: ProjectsServiceMock;
  101. const original = window.location;
  102. beforeAll(() => {
  103. Object.defineProperty(window, 'location', {
  104. configurable: true,
  105. value: { replace: jest.fn() },
  106. });
  107. almSettingsHandler = new AlmSettingsServiceMock();
  108. dopTranslationHandler = new DopTranslationServiceMock();
  109. newCodePeriodHandler = new NewCodeDefinitionServiceMock();
  110. projectHandler = new ProjectsServiceMock();
  111. });
  112. beforeEach(() => {
  113. jest.clearAllMocks();
  114. almSettingsHandler.reset();
  115. dopTranslationHandler.reset();
  116. newCodePeriodHandler.reset();
  117. projectHandler.reset();
  118. });
  119. afterAll(() => {
  120. Object.defineProperty(window, 'location', { configurable: true, value: original });
  121. });
  122. it('should fill form and move to NCD selection', async () => {
  123. const user = userEvent.setup();
  124. renderCreateProject();
  125. await fillFormAndNext('test', user);
  126. expect(ui.newCodeDefinitionHeader.get()).toBeInTheDocument();
  127. });
  128. it('should select the global NCD when it is compliant', async () => {
  129. jest
  130. .mocked(getNewCodeDefinition)
  131. .mockResolvedValue({ type: NewCodeDefinitionType.NumberOfDays, value: '30' });
  132. const user = userEvent.setup();
  133. renderCreateProject();
  134. await fillFormAndNext('test', user);
  135. expect(ui.newCodeDefinitionHeader.get()).toBeInTheDocument();
  136. expect(ui.inheritGlobalNcdRadio.get()).toBeInTheDocument();
  137. expect(ui.inheritGlobalNcdRadio.get()).toBeEnabled();
  138. expect(ui.projectCreateButton.get()).toBeDisabled();
  139. await user.click(ui.inheritGlobalNcdRadio.get());
  140. expect(ui.projectCreateButton.get()).toBeEnabled();
  141. });
  142. it('number of days ignores non-numeric inputs', async () => {
  143. jest
  144. .mocked(getNewCodeDefinition)
  145. .mockResolvedValue({ type: NewCodeDefinitionType.NumberOfDays, value: '60' });
  146. const user = userEvent.setup();
  147. renderCreateProject();
  148. await fillFormAndNext('test', user);
  149. expect(ui.projectCreateButton.get()).toBeDisabled();
  150. expect(ui.overrideNcdRadio.get()).not.toHaveClass('disabled');
  151. expect(ui.ncdOptionDaysRadio.get()).toHaveClass('disabled');
  152. await user.click(ui.overrideNcdRadio.get());
  153. expect(ui.ncdOptionDaysRadio.get()).not.toHaveClass('disabled');
  154. await user.click(ui.ncdOptionDaysRadio.get());
  155. expect(ui.ncdOptionDaysInput.get()).toBeInTheDocument();
  156. expect(ui.ncdOptionDaysInput.get()).toHaveValue(60);
  157. expect(ui.projectCreateButton.get()).toBeEnabled();
  158. await user.click(ui.ncdOptionDaysInput.get());
  159. await user.keyboard('abc');
  160. // it ignores the input and preserves its value
  161. expect(ui.ncdOptionDaysInput.get()).toHaveValue(60);
  162. });
  163. it('the project onboarding page should be displayed when the project is created', async () => {
  164. newCodePeriodHandler.setNewCodePeriod({ type: NewCodeDefinitionType.NumberOfDays });
  165. const user = userEvent.setup();
  166. renderCreateProject();
  167. await fillFormAndNext('testing', user);
  168. await user.click(ui.overrideNcdRadio.get());
  169. expect(ui.projectCreateButton.get()).toBeEnabled();
  170. await user.click(ui.projectCreateButton.get());
  171. expect(await ui.projectDashboardText.find()).toBeInTheDocument();
  172. });
  173. it('validate the private key field', async () => {
  174. const user = userEvent.setup();
  175. renderCreateProject();
  176. expect(ui.manualProjectHeader.get()).toBeInTheDocument();
  177. await user.click(ui.displayNameField.get());
  178. await user.keyboard('exists');
  179. await waitFor(() => {
  180. expect(ui.projectNextButton.get()).toBeDisabled();
  181. });
  182. await user.click(ui.projectNextButton.get());
  183. });
  184. it('should navigate back to the Projects page when clicking cancel or close', async () => {
  185. newCodePeriodHandler.setNewCodePeriod({ type: NewCodeDefinitionType.NumberOfDays });
  186. const user = userEvent.setup();
  187. renderCreateProject();
  188. await user.click(ui.cancelButton.get());
  189. expect(await ui.projectsPageTitle.find()).toBeInTheDocument();
  190. await user.click(ui.createProjectsButton.get());
  191. await user.click(await ui.createLocalProject.find());
  192. await user.click(ui.closeButton.get());
  193. expect(await ui.projectsPageTitle.find()).toBeInTheDocument();
  194. await user.click(ui.createProjectsButton.get());
  195. await user.click(await ui.createLocalProject.find());
  196. expect(await ui.manualProjectHeader.find()).toBeInTheDocument();
  197. await fillFormAndNext('testing', user);
  198. expect(ui.newCodeDefinitionHeader.get()).toBeInTheDocument();
  199. await user.click(await ui.newCodeDefinitionSection.byRole('button', { name: 'clear' }).find());
  200. expect(await ui.projectsPageTitle.find()).toBeInTheDocument();
  201. });
  202. function renderCreateProject() {
  203. renderAppRoutes('projects/create?mode=manual', routes, {
  204. currentUser: mockCurrentUser({
  205. permissions: { global: [Permissions.ProjectCreation] },
  206. }),
  207. appState: mockAppState({ canAdmin: true }),
  208. });
  209. }