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.

IssuesApp-Filtering-it.tsx 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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, within } from '@testing-library/react';
  21. import userEvent from '@testing-library/user-event';
  22. import React from 'react';
  23. import { renderOwaspTop102021Category } from '../../../helpers/security-standard';
  24. import { mockLoggedInUser, mockRawIssue } from '../../../helpers/testMocks';
  25. import { NoticeType } from '../../../types/users';
  26. import IssuesList from '../components/IssuesList';
  27. import {
  28. branchHandler,
  29. componentsHandler,
  30. issuesHandler,
  31. renderIssueApp,
  32. renderProjectIssuesApp,
  33. ui,
  34. waitOnDataLoaded,
  35. } from '../test-utils';
  36. jest.mock('../components/IssuesList', () => {
  37. const fakeIssueList = (props: IssuesList['props']) => {
  38. return (
  39. <>
  40. {props.issues.map((i) => (
  41. <section key={i.key} aria-label={i.message}>
  42. {i.message}
  43. </section>
  44. ))}
  45. </>
  46. );
  47. };
  48. return {
  49. __esModule: true,
  50. default: fakeIssueList,
  51. };
  52. });
  53. beforeEach(() => {
  54. issuesHandler.reset();
  55. componentsHandler.reset();
  56. branchHandler.reset();
  57. window.scrollTo = jest.fn();
  58. window.HTMLElement.prototype.scrollTo = jest.fn();
  59. });
  60. describe('issues app filtering', () => {
  61. it('should combine sidebar filters properly', async () => {
  62. jest.useFakeTimers();
  63. const user = userEvent.setup({ delay: null });
  64. renderIssueApp();
  65. await waitOnDataLoaded();
  66. // Select CC responsible category (should make the first issue disappear)
  67. await user.click(ui.responsibleCategoryFilter.get());
  68. expect(ui.issueItem1.query()).not.toBeInTheDocument();
  69. // Select responsible + Maintainability quality
  70. await user.click(ui.softwareQualityMaintainabilityFilter.get());
  71. expect(ui.issueItem5.query()).not.toBeInTheDocument();
  72. // Select MEDIUM severity
  73. await user.click(ui.severityFacet.get());
  74. await user.click(ui.mediumSeverityFilter.get());
  75. expect(ui.issueItem8.query()).not.toBeInTheDocument();
  76. // Expand scope and set code smells + major severity + main scope
  77. await user.click(ui.scopeFacet.get());
  78. await user.click(ui.mainScopeFilter.get());
  79. expect(ui.issueItem4.query()).not.toBeInTheDocument();
  80. // Check that filters were applied as expected
  81. expect(ui.issueItem6.get()).toBeInTheDocument();
  82. // Status
  83. await user.click(ui.issueStatusFacet.get());
  84. await user.click(ui.openStatusFilter.get());
  85. expect(ui.issueItem6.query()).not.toBeInTheDocument(); // Issue 6 should vanish
  86. // Ctrl+click on confirmed status
  87. await user.keyboard('{Control>}');
  88. await user.click(ui.confirmedStatusFilter.get());
  89. await user.keyboard('{/Control}');
  90. expect(ui.issueItem6.get()).toBeInTheDocument(); // Issue 6 should come back
  91. // Rule
  92. await user.click(ui.ruleFacet.get());
  93. await user.click(screen.getByRole('checkbox', { name: 'other' }));
  94. // Name should apply to the rule
  95. expect(screen.getByRole('checkbox', { name: '(HTML) Advanced rule' })).toBeInTheDocument();
  96. // Tag
  97. await user.click(ui.tagFacet.get());
  98. await user.type(ui.tagFacetSearch.get(), 'unu');
  99. await user.click(screen.getByRole('checkbox', { name: 'unused' }));
  100. // Project
  101. await user.click(ui.projectFacet.get());
  102. await user.click(screen.getByRole('checkbox', { name: 'org.project2' }));
  103. // Assignee
  104. await user.click(ui.assigneeFacet.get());
  105. await user.click(screen.getByRole('checkbox', { name: 'email2@sonarsource.com' }));
  106. await user.click(screen.getByRole('checkbox', { name: 'email1@sonarsource.com' })); // Change assignee
  107. // Author
  108. await user.click(ui.authorFacet.get());
  109. await user.type(ui.authorFacetSearch.get(), 'email');
  110. await user.click(screen.getByRole('checkbox', { name: 'email4@sonarsource.com' }));
  111. await user.click(screen.getByRole('checkbox', { name: 'email3@sonarsource.com' })); // Change author
  112. // Deprecated type
  113. await user.click(ui.typeFacet.get());
  114. await user.click(ui.codeSmellIssueTypeFilter.get());
  115. expect(ui.issueItem1.query()).not.toBeInTheDocument();
  116. expect(ui.issueItem2.query()).not.toBeInTheDocument();
  117. expect(ui.issueItem3.query()).not.toBeInTheDocument();
  118. expect(ui.issueItem4.query()).not.toBeInTheDocument();
  119. expect(ui.issueItem5.query()).not.toBeInTheDocument();
  120. expect(ui.issueItem6.query()).not.toBeInTheDocument();
  121. expect(ui.issueItem7.get()).toBeInTheDocument();
  122. // Clear filters one by one
  123. await user.click(ui.clearCodeCategoryFacet.get());
  124. await user.click(ui.clearSoftwareQualityFacet.get());
  125. await user.click(ui.clearIssueTypeFacet.get());
  126. await user.click(ui.clearSeverityFacet.get());
  127. await user.click(ui.clearScopeFacet.get());
  128. await user.click(ui.clearRuleFacet.get());
  129. await user.click(ui.clearTagFacet.get());
  130. await user.click(ui.clearProjectFacet.get());
  131. await user.click(ui.clearAssigneeFacet.get());
  132. await user.click(ui.clearAuthorFacet.get());
  133. expect(ui.issueItem1.get()).toBeInTheDocument();
  134. expect(ui.issueItem2.get()).toBeInTheDocument();
  135. expect(ui.issueItem3.get()).toBeInTheDocument();
  136. expect(ui.issueItem4.get()).toBeInTheDocument();
  137. expect(ui.issueItem5.get()).toBeInTheDocument();
  138. expect(ui.issueItem6.get()).toBeInTheDocument();
  139. expect(ui.issueItem7.get()).toBeInTheDocument();
  140. jest.useRealTimers();
  141. });
  142. it('should properly filter by code variants', async () => {
  143. const user = userEvent.setup();
  144. renderProjectIssuesApp();
  145. await waitOnDataLoaded();
  146. await user.click(ui.codeVariantsFacet.get());
  147. await user.click(screen.getByRole('checkbox', { name: /variant 1/ }));
  148. expect(ui.issueItem1.query()).not.toBeInTheDocument();
  149. expect(ui.issueItem7.get()).toBeInTheDocument();
  150. // Clear filter
  151. await user.click(ui.clearCodeVariantsFacet.get());
  152. expect(ui.issueItem1.get()).toBeInTheDocument();
  153. });
  154. it('should properly hide the code variants filter if no issue has any code variants', async () => {
  155. issuesHandler.setIssueList([
  156. {
  157. issue: mockRawIssue(),
  158. snippets: {},
  159. },
  160. ]);
  161. renderProjectIssuesApp();
  162. await waitOnDataLoaded();
  163. expect(ui.codeVariantsFacet.query()).not.toBeInTheDocument();
  164. });
  165. it('should allow to set creation date', async () => {
  166. const user = userEvent.setup();
  167. const currentUser = mockLoggedInUser({ dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true } });
  168. issuesHandler.setCurrentUser(currentUser);
  169. renderIssueApp(currentUser);
  170. await waitOnDataLoaded();
  171. // Select a specific date range such that only one issue matches
  172. await user.click(ui.creationDateFacet.get());
  173. await user.click(screen.getByPlaceholderText('start_date'));
  174. const monthSelector = within(ui.dateInputMonthSelect.get()).getByRole('combobox');
  175. await user.click(monthSelector);
  176. await user.click(within(ui.dateInputMonthSelect.get()).getByText('Jan'));
  177. const yearSelector = within(ui.dateInputYearSelect.get()).getByRole('combobox');
  178. await user.click(yearSelector);
  179. await user.click(within(ui.dateInputYearSelect.get()).getAllByText('2023')[-1]);
  180. await user.click(screen.getByText('1', { selector: 'button' }));
  181. await user.click(screen.getByText('10'));
  182. expect(ui.issueItem1.get()).toBeInTheDocument();
  183. expect(ui.issueItem2.query()).not.toBeInTheDocument();
  184. expect(ui.issueItem3.query()).not.toBeInTheDocument();
  185. expect(ui.issueItem4.query()).not.toBeInTheDocument();
  186. expect(ui.issueItem5.query()).not.toBeInTheDocument();
  187. expect(ui.issueItem6.query()).not.toBeInTheDocument();
  188. expect(ui.issueItem7.query()).not.toBeInTheDocument();
  189. });
  190. it('should allow to only show my issues', async () => {
  191. const user = userEvent.setup();
  192. const currentUser = mockLoggedInUser({ dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true } });
  193. issuesHandler.setCurrentUser(currentUser);
  194. renderIssueApp(currentUser);
  195. await waitOnDataLoaded();
  196. // By default, it should show all issues
  197. expect(ui.issueItem2.get()).toBeInTheDocument();
  198. expect(ui.issueItem3.get()).toBeInTheDocument();
  199. // Only show my issues
  200. await user.click(screen.getByRole('radio', { name: 'issues.my_issues' }));
  201. expect(ui.issueItem2.query()).not.toBeInTheDocument();
  202. expect(ui.issueItem3.get()).toBeInTheDocument();
  203. // Show all issues again
  204. await user.click(screen.getByRole('radio', { name: 'all' }));
  205. expect(ui.issueItem2.get()).toBeInTheDocument();
  206. expect(ui.issueItem3.get()).toBeInTheDocument();
  207. });
  208. it('should search for rules with proper types', async () => {
  209. const user = userEvent.setup();
  210. renderIssueApp();
  211. await user.click(await ui.ruleFacet.find());
  212. await user.type(ui.ruleFacetSearch.get(), 'rule');
  213. expect(within(ui.ruleFacetList.get()).getAllByRole('checkbox')).toHaveLength(2);
  214. expect(
  215. within(ui.ruleFacetList.get()).getByRole('checkbox', {
  216. name: /Advanced rule/,
  217. }),
  218. ).toBeInTheDocument();
  219. expect(
  220. within(ui.ruleFacetList.get()).getByRole('checkbox', {
  221. name: /Simple rule/,
  222. }),
  223. ).toBeInTheDocument();
  224. });
  225. it('should update collapsed facets with filter change', async () => {
  226. const user = userEvent.setup();
  227. renderIssueApp();
  228. await user.click(await ui.languageFacet.find());
  229. expect(await ui.languageFacetList.find()).toBeInTheDocument();
  230. expect(
  231. within(ui.languageFacetList.get()).getByRole('checkbox', { name: 'java' }),
  232. ).toHaveTextContent('java25short_number_suffix.k');
  233. expect(
  234. within(ui.languageFacetList.get()).getByRole('checkbox', { name: 'ts' }),
  235. ).toHaveTextContent('ts3.4short_number_suffix.k');
  236. await user.click(ui.languageFacet.get());
  237. expect(ui.languageFacetList.query()).not.toBeInTheDocument();
  238. await user.click(ui.responsibleCategoryFilter.get());
  239. await user.click(ui.languageFacet.get());
  240. expect(await ui.languageFacetList.find()).toBeInTheDocument();
  241. expect(
  242. within(ui.languageFacetList.get()).getByRole('checkbox', { name: 'java' }),
  243. ).toHaveTextContent('java111');
  244. expect(
  245. within(ui.languageFacetList.get()).getByRole('checkbox', { name: 'ts' }),
  246. ).toHaveTextContent('ts674');
  247. });
  248. it('should show the new code issues only', async () => {
  249. const user = userEvent.setup();
  250. renderProjectIssuesApp('project/issues?id=myproject');
  251. expect(await ui.issueItems.findAll()).toHaveLength(7);
  252. await user.click(await ui.inNewCodeFilter.find());
  253. expect(await ui.issueItems.findAll()).toHaveLength(6);
  254. });
  255. it('should support OWASP Top 10 version 2021', async () => {
  256. const user = userEvent.setup();
  257. renderIssueApp();
  258. await user.click(screen.getByRole('button', { name: 'issues.facet.standards' }));
  259. const owaspTop102021 = screen.getByRole('button', { name: 'issues.facet.owaspTop10_2021' });
  260. expect(owaspTop102021).toBeInTheDocument();
  261. await user.click(owaspTop102021);
  262. await Promise.all(
  263. issuesHandler.owasp2021FacetList().values.map(async ({ val }) => {
  264. const standard = await issuesHandler.getStandards();
  265. /* eslint-disable-next-line testing-library/render-result-naming-convention */
  266. const linkName = renderOwaspTop102021Category(standard, val);
  267. expect(screen.getByRole('checkbox', { name: linkName })).toBeInTheDocument();
  268. }),
  269. );
  270. });
  271. });
  272. describe('issues app when reindexing', () => {
  273. it('should display only some facets while reindexing is in progress', () => {
  274. issuesHandler.setIsAdmin(true);
  275. renderProjectIssuesApp(undefined, { needIssueSync: true });
  276. // Enabled facets
  277. expect(ui.inNewCodeFilter.get()).toBeInTheDocument();
  278. expect(ui.typeFacet.get()).toBeInTheDocument();
  279. // Disabled facets
  280. expect(ui.cleanCodeAttributeCategoryFacet.query()).not.toBeInTheDocument();
  281. expect(ui.softwareQualityFacet.query()).not.toBeInTheDocument();
  282. expect(ui.assigneeFacet.query()).not.toBeInTheDocument();
  283. expect(ui.authorFacet.query()).not.toBeInTheDocument();
  284. expect(ui.codeVariantsFacet.query()).not.toBeInTheDocument();
  285. expect(ui.creationDateFacet.query()).not.toBeInTheDocument();
  286. expect(ui.languageFacet.query()).not.toBeInTheDocument();
  287. expect(ui.projectFacet.query()).not.toBeInTheDocument();
  288. expect(ui.resolutionFacet.query()).not.toBeInTheDocument();
  289. expect(ui.ruleFacet.query()).not.toBeInTheDocument();
  290. expect(ui.scopeFacet.query()).not.toBeInTheDocument();
  291. expect(ui.issueStatusFacet.query()).not.toBeInTheDocument();
  292. expect(ui.tagFacet.query()).not.toBeInTheDocument();
  293. // Indexation message
  294. expect(screen.getByText(/indexation\.filters_unavailable/)).toBeInTheDocument();
  295. });
  296. });