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.

App-it.tsx 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 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 selectEvent from 'react-select-event';
  23. import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock';
  24. import { searchProjects, searchUsers } from '../../../../api/quality-gates';
  25. import { mockAppState } from '../../../../helpers/testMocks';
  26. import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
  27. import { AppState } from '../../../../types/appstate';
  28. import routes from '../../routes';
  29. jest.mock('../../../../api/quality-gates');
  30. let handler: QualityGatesServiceMock;
  31. beforeAll(() => {
  32. handler = new QualityGatesServiceMock();
  33. });
  34. afterEach(() => handler.reset());
  35. it('should open the default quality gates', async () => {
  36. renderQualityGateApp();
  37. expect(await screen.findAllByRole('menuitem')).toHaveLength(handler.list.length);
  38. const defaultQualityGate = handler.getDefaultQualityGate();
  39. expect(await screen.findAllByText(defaultQualityGate.name)).toHaveLength(2);
  40. });
  41. it('should list all quality gates', async () => {
  42. renderQualityGateApp();
  43. expect(
  44. await screen.findByRole('menuitem', {
  45. name: `${handler.getDefaultQualityGate().name} default`
  46. })
  47. ).toBeInTheDocument();
  48. expect(
  49. await screen.findByRole('menuitem', {
  50. name: `${handler.getBuiltInQualityGate().name} quality_gates.built_in`
  51. })
  52. ).toBeInTheDocument();
  53. });
  54. it('should be able to create a quality gate then delete it', async () => {
  55. const user = userEvent.setup();
  56. handler.setIsAdmin(true);
  57. renderQualityGateApp();
  58. let createButton = await screen.findByRole('button', { name: 'create' });
  59. // Using keyboard
  60. await user.click(createButton);
  61. let nameInput = screen.getByRole('textbox', { name: /name.*/ });
  62. expect(nameInput).toBeInTheDocument();
  63. await user.click(nameInput);
  64. await user.keyboard('testone{Enter}');
  65. expect(await screen.findByRole('menuitem', { name: 'testone' })).toBeInTheDocument();
  66. // Using modal button
  67. createButton = await screen.findByRole('button', { name: 'create' });
  68. await user.click(createButton);
  69. nameInput = screen.getByRole('textbox', { name: /name.*/ });
  70. const saveButton = screen.getByRole('button', { name: 'save' });
  71. expect(saveButton).toBeDisabled();
  72. await user.click(nameInput);
  73. await user.keyboard('testtwo');
  74. await user.click(saveButton);
  75. const newQG = await screen.findByRole('menuitem', { name: 'testtwo' });
  76. expect(newQG).toBeInTheDocument();
  77. // Delete the quality gate
  78. await user.click(newQG);
  79. const deleteButton = await screen.findByRole('button', { name: 'delete' });
  80. await user.click(deleteButton);
  81. const popup = screen.getByRole('dialog');
  82. const dialogDeleteButton = within(popup).getByRole('button', { name: 'delete' });
  83. await user.click(dialogDeleteButton);
  84. await waitFor(() => {
  85. expect(screen.queryByRole('menuitem', { name: 'testtwo' })).not.toBeInTheDocument();
  86. });
  87. });
  88. it('should be able to copy a quality gate', async () => {
  89. const user = userEvent.setup();
  90. handler.setIsAdmin(true);
  91. renderQualityGateApp();
  92. const copyButton = await screen.findByRole('button', { name: 'copy' });
  93. await user.click(copyButton);
  94. const nameInput = screen.getByRole('textbox', { name: /name.*/ });
  95. expect(nameInput).toBeInTheDocument();
  96. await user.click(nameInput);
  97. await user.keyboard(' bis{Enter}');
  98. expect(await screen.findByRole('menuitem', { name: /.* bis/ })).toBeInTheDocument();
  99. });
  100. it('should be able to rename a quality gate', async () => {
  101. const user = userEvent.setup();
  102. handler.setIsAdmin(true);
  103. renderQualityGateApp();
  104. const renameButton = await screen.findByRole('button', { name: 'rename' });
  105. await user.click(renameButton);
  106. const nameInput = screen.getByRole('textbox', { name: /name.*/ });
  107. expect(nameInput).toBeInTheDocument();
  108. await user.click(nameInput);
  109. await user.keyboard('{Control>}a{/Control}New Name{Enter}');
  110. expect(await screen.findByRole('menuitem', { name: /New Name.*/ })).toBeInTheDocument();
  111. });
  112. it('should be able to set as default a quality gate', async () => {
  113. const user = userEvent.setup();
  114. handler.setIsAdmin(true);
  115. renderQualityGateApp();
  116. const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
  117. await user.click(notDefaultQualityGate);
  118. const setAsDefaultButton = screen.getByRole('button', { name: 'set_as_default' });
  119. await user.click(setAsDefaultButton);
  120. expect(screen.getAllByRole('menuitem')[1]).toHaveTextContent('default');
  121. });
  122. it('should be able to add a condition', async () => {
  123. const user = userEvent.setup();
  124. handler.setIsAdmin(true);
  125. renderQualityGateApp();
  126. // On new code
  127. await user.click(await screen.findByText('quality_gates.add_condition'));
  128. let dialog = within(screen.getByRole('dialog'));
  129. await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.new_code' }));
  130. await selectEvent.select(dialog.getByRole('textbox'), ['Issues']);
  131. await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' }));
  132. await user.keyboard('12{Enter}');
  133. const newConditions = within(
  134. await screen.findByRole('table', { name: 'quality_gates.conditions.new_code.long' })
  135. );
  136. expect(await newConditions.findByRole('cell', { name: 'Issues' })).toBeInTheDocument();
  137. expect(await newConditions.findByRole('cell', { name: '12' })).toBeInTheDocument();
  138. // On overall code
  139. await user.click(await screen.findByText('quality_gates.add_condition'));
  140. dialog = within(screen.getByRole('dialog'));
  141. await selectEvent.select(dialog.getByRole('textbox'), ['Info Issues']);
  142. await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.overall_code' }));
  143. await user.click(dialog.getByLabelText('quality_gates.conditions.operator'));
  144. await user.click(dialog.getByText('quality_gates.operator.LT'));
  145. await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' }));
  146. await user.keyboard('42{Enter}');
  147. let overallConditions = within(
  148. await screen.findByRole('table', { name: 'quality_gates.conditions.overall_code.long' })
  149. );
  150. expect(await overallConditions.findByRole('cell', { name: 'Info Issues' })).toBeInTheDocument();
  151. expect(await overallConditions.findByRole('cell', { name: '42' })).toBeInTheDocument();
  152. // Select a rating
  153. await user.click(await screen.findByText('quality_gates.add_condition'));
  154. dialog = within(screen.getByRole('dialog'));
  155. await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.overall_code' }));
  156. await selectEvent.select(dialog.getByRole('textbox'), ['Maintainability Rating']);
  157. await user.click(dialog.getByLabelText('quality_gates.conditions.value'));
  158. await user.click(dialog.getByText('B'));
  159. await user.click(dialog.getByRole('button', { name: 'quality_gates.add_condition' }));
  160. overallConditions = within(
  161. await screen.findByRole('table', { name: 'quality_gates.conditions.overall_code.long' })
  162. );
  163. expect(
  164. await overallConditions.findByRole('cell', { name: 'Maintainability Rating' })
  165. ).toBeInTheDocument();
  166. expect(await overallConditions.findByRole('cell', { name: 'B' })).toBeInTheDocument();
  167. });
  168. it('should be able to edit a condition', async () => {
  169. const user = userEvent.setup();
  170. handler.setIsAdmin(true);
  171. renderQualityGateApp();
  172. const newConditions = within(
  173. await screen.findByRole('table', {
  174. name: 'quality_gates.conditions.new_code.long'
  175. })
  176. );
  177. await user.click(
  178. newConditions.getByLabelText('quality_gates.condition.edit.Coverage on New Code')
  179. );
  180. const dialog = within(screen.getByRole('dialog'));
  181. await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' }));
  182. await user.keyboard('{Backspace}{Backspace}23{Enter}');
  183. expect(await newConditions.findByText('Coverage')).toBeInTheDocument();
  184. expect(await newConditions.findByText('23.0%')).toBeInTheDocument();
  185. });
  186. it('should be able to handle duplicate or deprecated condition', async () => {
  187. const user = userEvent.setup();
  188. handler.setIsAdmin(true);
  189. renderQualityGateApp();
  190. await user.click(
  191. await screen.findByRole('menuitem', { name: handler.getCorruptedQualityGateName() })
  192. );
  193. expect(await screen.findByText('quality_gates.duplicated_conditions')).toBeInTheDocument();
  194. expect(
  195. await screen.findByRole('cell', { name: 'Complexity / Function deprecated' })
  196. ).toBeInTheDocument();
  197. });
  198. it('should be able to handle delete condition', async () => {
  199. const user = userEvent.setup();
  200. handler.setIsAdmin(true);
  201. renderQualityGateApp();
  202. const newConditions = within(
  203. await screen.findByRole('table', {
  204. name: 'quality_gates.conditions.new_code.long'
  205. })
  206. );
  207. await user.click(
  208. newConditions.getByLabelText('quality_gates.condition.delete.Coverage on New Code')
  209. );
  210. const dialog = within(screen.getByRole('dialog'));
  211. await user.click(dialog.getByRole('button', { name: 'delete' }));
  212. await waitFor(() => {
  213. expect(newConditions.queryByRole('cell', { name: 'Coverage' })).not.toBeInTheDocument();
  214. });
  215. });
  216. it('should explain condition on branch', async () => {
  217. renderQualityGateApp(mockAppState({ branchesEnabled: true }));
  218. expect(
  219. await screen.findByText('quality_gates.conditions.new_code.description')
  220. ).toBeInTheDocument();
  221. expect(
  222. await screen.findByText('quality_gates.conditions.overall_code.description')
  223. ).toBeInTheDocument();
  224. });
  225. describe('The Project section', () => {
  226. it('should render list of projects correctly in different tabs', async () => {
  227. const user = userEvent.setup();
  228. handler.setIsAdmin(true);
  229. renderQualityGateApp();
  230. const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
  231. await user.click(notDefaultQualityGate);
  232. const projectSelectList = screen.getByRole('group');
  233. // by default it shows "selected" values
  234. expect(within(projectSelectList).getAllByRole('button')).toHaveLength(3);
  235. expect(screen.getAllByRole('checkbox')).toHaveLength(2);
  236. // change tabs to show deselected projects
  237. await user.click(
  238. within(projectSelectList).getByRole('button', { name: 'quality_gates.projects.without' })
  239. );
  240. expect(screen.getAllByRole('checkbox')).toHaveLength(2);
  241. // change tabs to show all projects
  242. await user.click(
  243. within(projectSelectList).getByRole('button', { name: 'quality_gates.projects.all' })
  244. );
  245. expect(screen.getAllByRole('checkbox')).toHaveLength(4);
  246. });
  247. it('should handle select and deselect correctly', async () => {
  248. const user = userEvent.setup();
  249. handler.setIsAdmin(true);
  250. renderQualityGateApp();
  251. const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
  252. await user.click(notDefaultQualityGate);
  253. const projectSelectList = screen.getByRole('group');
  254. const checkedProjects = screen.getAllByRole('checkbox')[0];
  255. expect(screen.getAllByRole('checkbox')).toHaveLength(2);
  256. await user.click(checkedProjects);
  257. const reloadButton = screen.getByRole('button', { name: 'reload' });
  258. expect(reloadButton).toBeInTheDocument();
  259. await user.click(reloadButton);
  260. expect(screen.getAllByRole('checkbox')).toHaveLength(1);
  261. // change tabs to show deselected projects
  262. await user.click(within(projectSelectList).getAllByRole('button')[1]);
  263. const uncheckedProjects = screen.getAllByRole('checkbox')[0];
  264. expect(screen.getAllByRole('checkbox')).toHaveLength(3);
  265. await user.click(uncheckedProjects);
  266. expect(reloadButton).toBeInTheDocument();
  267. await user.click(reloadButton);
  268. expect(screen.getAllByRole('checkbox')).toHaveLength(2);
  269. });
  270. it('should handle the search of projects', async () => {
  271. const user = userEvent.setup();
  272. handler.setIsAdmin(true);
  273. renderQualityGateApp();
  274. const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
  275. await user.click(notDefaultQualityGate);
  276. const searchInput = screen.getByRole('searchbox', { name: 'search_verb' });
  277. expect(searchInput).toBeInTheDocument();
  278. await user.click(searchInput);
  279. await user.keyboard('test2{Enter}');
  280. expect(screen.getAllByRole('checkbox')).toHaveLength(1);
  281. });
  282. it('should display show more button if there are multiple pages of data', async () => {
  283. (searchProjects as jest.Mock).mockResolvedValueOnce({
  284. paging: { pageIndex: 2, pageSize: 3, total: 55 },
  285. results: []
  286. });
  287. const user = userEvent.setup();
  288. handler.setIsAdmin(true);
  289. renderQualityGateApp();
  290. const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
  291. await user.click(notDefaultQualityGate);
  292. expect(screen.getByRole('button', { name: 'show_more' })).toBeInTheDocument();
  293. });
  294. });
  295. describe('The Permissions section', () => {
  296. it('should not show button to grant permission when user is not admin', async () => {
  297. renderQualityGateApp();
  298. // await just to make sure we've loaded the page
  299. expect(
  300. await screen.findByRole('menuitem', {
  301. name: `${handler.getDefaultQualityGate().name} default`
  302. })
  303. ).toBeInTheDocument();
  304. expect(screen.queryByText('quality_gates.permissions')).not.toBeInTheDocument();
  305. });
  306. it('should show button to grant permission when user is admin', async () => {
  307. handler.setIsAdmin(true);
  308. renderQualityGateApp();
  309. const grantPermissionButton = await screen.findByRole('button', {
  310. name: 'quality_gates.permissions.grant'
  311. });
  312. expect(screen.getByText('quality_gates.permissions')).toBeInTheDocument();
  313. expect(grantPermissionButton).toBeInTheDocument();
  314. });
  315. it('should assign permission to a user and delete it later', async () => {
  316. const user = userEvent.setup();
  317. handler.setIsAdmin(true);
  318. renderQualityGateApp();
  319. expect(screen.queryByText('userlogin')).not.toBeInTheDocument();
  320. // Granting permission to a user
  321. const grantPermissionButton = await screen.findByRole('button', {
  322. name: 'quality_gates.permissions.grant'
  323. });
  324. await user.click(grantPermissionButton);
  325. const popup = screen.getByRole('dialog');
  326. const searchUserInput = within(popup).getByRole('textbox');
  327. expect(searchUserInput).toBeInTheDocument();
  328. const addUserButton = screen.getByRole('button', {
  329. name: 'add_verb'
  330. });
  331. expect(addUserButton).toBeDisabled();
  332. await user.click(searchUserInput);
  333. expect(screen.getAllByTestId('qg-add-permission-option')).toHaveLength(2);
  334. await user.click(screen.getByText('userlogin'));
  335. expect(addUserButton).toBeEnabled();
  336. await user.click(addUserButton);
  337. expect(screen.getByText('userlogin')).toBeInTheDocument();
  338. // Cancel granting permission
  339. await user.click(grantPermissionButton);
  340. await user.click(searchUserInput);
  341. await user.keyboard('test{Enter}');
  342. const cancelButton = screen.getByRole('button', {
  343. name: 'cancel'
  344. });
  345. await user.click(cancelButton);
  346. expect(screen.getAllByRole('listitem')).toHaveLength(1);
  347. // Delete the user permission
  348. const deleteButton = screen.getByTestId('permission-delete-button');
  349. await user.click(deleteButton);
  350. const deletePopup = screen.getByRole('dialog');
  351. const dialogDeleteButton = within(deletePopup).getByRole('button', { name: 'remove' });
  352. await user.click(dialogDeleteButton);
  353. expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
  354. });
  355. it('should assign permission to a group and delete it later', async () => {
  356. const user = userEvent.setup();
  357. handler.setIsAdmin(true);
  358. renderQualityGateApp();
  359. expect(screen.queryByText('userlogin')).not.toBeInTheDocument();
  360. // Granting permission to a group
  361. const grantPermissionButton = await screen.findByRole('button', {
  362. name: 'quality_gates.permissions.grant'
  363. });
  364. await user.click(grantPermissionButton);
  365. const popup = screen.getByRole('dialog');
  366. const searchUserInput = within(popup).getByRole('textbox');
  367. const addUserButton = screen.getByRole('button', {
  368. name: 'add_verb'
  369. });
  370. await user.click(searchUserInput);
  371. expect(screen.getAllByTestId('qg-add-permission-option')).toHaveLength(2);
  372. await user.click(screen.getAllByTestId('qg-add-permission-option')[1]);
  373. await user.click(addUserButton);
  374. expect(screen.getByText('Foo')).toBeInTheDocument();
  375. // Delete the group permission
  376. const deleteButton = screen.getByTestId('permission-delete-button');
  377. await user.click(deleteButton);
  378. const deletePopup = screen.getByRole('dialog');
  379. const dialogDeleteButton = within(deletePopup).getByRole('button', { name: 'remove' });
  380. await user.click(dialogDeleteButton);
  381. expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
  382. });
  383. it('should handle searchUser service failure', async () => {
  384. (searchUsers as jest.Mock).mockRejectedValue('error');
  385. const user = userEvent.setup();
  386. handler.setIsAdmin(true);
  387. renderQualityGateApp();
  388. const grantPermissionButton = await screen.findByRole('button', {
  389. name: 'quality_gates.permissions.grant'
  390. });
  391. await user.click(grantPermissionButton);
  392. const popup = screen.getByRole('dialog');
  393. const searchUserInput = within(popup).getByRole('textbox');
  394. await user.click(searchUserInput);
  395. expect(screen.getByText('no_results')).toBeInTheDocument();
  396. });
  397. });
  398. function renderQualityGateApp(appState?: AppState) {
  399. renderAppRoutes('quality_gates', routes, { appState });
  400. }