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.

ProjectActivityApp-it.tsx 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  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 { keyBy, times } from 'lodash';
  23. import React from 'react';
  24. import { Route } from 'react-router-dom';
  25. import ApplicationServiceMock from '../../../../api/mocks/ApplicationServiceMock';
  26. import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
  27. import { TimeMachineServiceMock } from '../../../../api/mocks/TimeMachineServiceMock';
  28. import { mockBranchList } from '../../../../api/mocks/data/branches';
  29. import { parseDate } from '../../../../helpers/dates';
  30. import { mockComponent } from '../../../../helpers/mocks/component';
  31. import {
  32. mockAnalysis,
  33. mockAnalysisEvent,
  34. mockHistoryItem,
  35. mockMeasureHistory,
  36. } from '../../../../helpers/mocks/project-activity';
  37. import { get } from '../../../../helpers/storage';
  38. import { mockMetric } from '../../../../helpers/testMocks';
  39. import { renderAppWithComponentContext } from '../../../../helpers/testReactTestingUtils';
  40. import { byLabelText, byRole, byTestId, byText } from '../../../../helpers/testSelector';
  41. import { ComponentQualifier } from '../../../../types/component';
  42. import { MetricKey, MetricType } from '../../../../types/metrics';
  43. import {
  44. ApplicationAnalysisEventCategory,
  45. GraphType,
  46. ProjectAnalysisEventCategory,
  47. } from '../../../../types/project-activity';
  48. import ProjectActivityAppContainer from '../ProjectActivityApp';
  49. jest.mock('../../../../api/projectActivity');
  50. jest.mock('../../../../api/time-machine');
  51. jest.mock('../../../../helpers/storage', () => ({
  52. ...jest.requireActual('../../../../helpers/storage'),
  53. get: jest.fn(),
  54. save: jest.fn(),
  55. }));
  56. jest.mock('../../../../api/branches', () => ({
  57. getBranches: () => {
  58. isBranchReady = true;
  59. return Promise.resolve(mockBranchList());
  60. },
  61. }));
  62. const applicationHandler = new ApplicationServiceMock();
  63. const projectActivityHandler = new ProjectActivityServiceMock();
  64. const timeMachineHandler = new TimeMachineServiceMock();
  65. let isBranchReady = false;
  66. beforeEach(() => {
  67. isBranchReady = false;
  68. jest.clearAllMocks();
  69. applicationHandler.reset();
  70. projectActivityHandler.reset();
  71. timeMachineHandler.reset();
  72. timeMachineHandler.setMeasureHistory(
  73. [
  74. MetricKey.violations,
  75. MetricKey.bugs,
  76. MetricKey.reliability_rating,
  77. MetricKey.code_smells,
  78. MetricKey.sqale_rating,
  79. MetricKey.security_hotspots_reviewed,
  80. MetricKey.security_review_rating,
  81. ].map((metric) =>
  82. mockMeasureHistory({
  83. metric,
  84. history: projectActivityHandler
  85. .getAnalysesList()
  86. .map(({ date }) => mockHistoryItem({ value: '3', date: parseDate(date) })),
  87. }),
  88. ),
  89. );
  90. });
  91. describe('rendering', () => {
  92. it('should render issues as default graph', async () => {
  93. const { ui } = getPageObject();
  94. renderProjectActivityAppContainer();
  95. await ui.appLoaded();
  96. expect(ui.graphTypeIssues.get()).toBeInTheDocument();
  97. });
  98. it('should render new code legend for applications', async () => {
  99. const { ui } = getPageObject();
  100. renderProjectActivityAppContainer(
  101. mockComponent({
  102. qualifier: ComponentQualifier.Application,
  103. breadcrumbs: [
  104. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Application },
  105. ],
  106. }),
  107. );
  108. await ui.appLoaded();
  109. expect(ui.newCodeLegend.get()).toBeInTheDocument();
  110. });
  111. it('should render new code legend for projects', async () => {
  112. const { ui } = getPageObject();
  113. renderProjectActivityAppContainer(
  114. mockComponent({
  115. qualifier: ComponentQualifier.Project,
  116. breadcrumbs: [
  117. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
  118. ],
  119. leakPeriodDate: parseDate('2017-03-01T22:00:00.000Z').toDateString(),
  120. }),
  121. );
  122. await ui.appLoaded();
  123. expect(ui.newCodeLegend.get()).toBeInTheDocument();
  124. });
  125. it.each([ComponentQualifier.Portfolio, ComponentQualifier.SubPortfolio])(
  126. 'should not render new code legend for %s',
  127. async (qualifier) => {
  128. const { ui } = getPageObject();
  129. renderProjectActivityAppContainer(
  130. mockComponent({
  131. qualifier,
  132. breadcrumbs: [{ key: 'breadcrumb', name: 'breadcrumb', qualifier }],
  133. }),
  134. );
  135. await ui.appLoaded({ doNotWaitForBranch: true });
  136. expect(ui.newCodeLegend.query()).not.toBeInTheDocument();
  137. },
  138. );
  139. it('should correctly show the baseline marker', async () => {
  140. const { ui } = getPageObject();
  141. renderProjectActivityAppContainer(
  142. mockComponent({
  143. leakPeriodDate: parseDate('2017-03-01T22:00:00.000Z').toDateString(),
  144. breadcrumbs: [
  145. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
  146. ],
  147. }),
  148. );
  149. await ui.appLoaded();
  150. expect(ui.baseline.get()).toBeInTheDocument();
  151. });
  152. it('should correctly show the baseline marker when first new code analysis is not present but baseline analysis is present', async () => {
  153. const { ui } = getPageObject();
  154. renderProjectActivityAppContainer(
  155. mockComponent({
  156. leakPeriodDate: parseDate('2017-03-03T22:00:00.000Z').toDateString(),
  157. breadcrumbs: [
  158. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
  159. ],
  160. }),
  161. );
  162. await ui.appLoaded();
  163. expect(ui.baseline.get()).toBeInTheDocument();
  164. });
  165. it('should not show the baseline marker when first new code analysis and baseline analysis is not present', async () => {
  166. const { ui } = getPageObject();
  167. renderProjectActivityAppContainer(
  168. mockComponent({
  169. leakPeriodDate: parseDate('2017-03-10T22:00:00.000Z').toDateString(),
  170. breadcrumbs: [
  171. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
  172. ],
  173. }),
  174. );
  175. await ui.appLoaded();
  176. expect(ui.baseline.query()).not.toBeInTheDocument();
  177. });
  178. it('should only show certain security hotspot-related metrics for a project', async () => {
  179. const { ui } = getPageObject();
  180. renderProjectActivityAppContainer(
  181. mockComponent({
  182. breadcrumbs: [
  183. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
  184. ],
  185. }),
  186. );
  187. await ui.changeGraphType(GraphType.custom);
  188. await ui.openMetricsDropdown();
  189. expect(ui.metricCheckbox(MetricKey.security_hotspots_reviewed).get()).toBeInTheDocument();
  190. expect(ui.metricCheckbox(MetricKey.security_review_rating).query()).not.toBeInTheDocument();
  191. });
  192. it.each([ComponentQualifier.Portfolio, ComponentQualifier.SubPortfolio])(
  193. 'should only show certain security hotspot-related metrics for a %s',
  194. async (qualifier) => {
  195. const { ui } = getPageObject();
  196. renderProjectActivityAppContainer(
  197. mockComponent({
  198. qualifier,
  199. breadcrumbs: [{ key: 'breadcrumb', name: 'breadcrumb', qualifier }],
  200. }),
  201. );
  202. await ui.changeGraphType(GraphType.custom);
  203. await ui.openMetricsDropdown();
  204. expect(ui.metricCheckbox(MetricKey.security_review_rating).get()).toBeInTheDocument();
  205. expect(
  206. ui.metricCheckbox(MetricKey.security_hotspots_reviewed).query(),
  207. ).not.toBeInTheDocument();
  208. },
  209. );
  210. });
  211. describe('CRUD', () => {
  212. it('should correctly create, update, and delete "VERSION" events', async () => {
  213. const { ui } = getPageObject();
  214. const initialValue = '1.1-SNAPSHOT';
  215. const updatedValue = '1.1--SNAPSHOT';
  216. renderProjectActivityAppContainer(
  217. mockComponent({
  218. breadcrumbs: [
  219. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
  220. ],
  221. configuration: { showHistory: true },
  222. }),
  223. );
  224. await ui.appLoaded();
  225. await ui.addVersionEvent('1.1.0.1', initialValue);
  226. expect(screen.getAllByText(initialValue).length).toBeGreaterThan(0);
  227. await ui.updateEvent(1, updatedValue);
  228. expect(screen.getAllByText(updatedValue).length).toBeGreaterThan(0);
  229. await ui.deleteEvent(0);
  230. expect(screen.queryByText(updatedValue)).not.toBeInTheDocument();
  231. });
  232. it('should correctly create, update, and delete "OTHER" events', async () => {
  233. const { ui } = getPageObject();
  234. const initialValue = 'Custom event name';
  235. const updatedValue = 'Custom event updated name';
  236. renderProjectActivityAppContainer(
  237. mockComponent({
  238. breadcrumbs: [
  239. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
  240. ],
  241. configuration: { showHistory: true },
  242. }),
  243. );
  244. await ui.appLoaded();
  245. await ui.addCustomEvent('1.1.0.1', initialValue);
  246. expect(screen.getAllByText(initialValue).length).toBeGreaterThan(0);
  247. await ui.updateEvent(1, updatedValue);
  248. expect(screen.getAllByText(updatedValue).length).toBeGreaterThan(0);
  249. await ui.deleteEvent(0);
  250. expect(screen.queryByText(updatedValue)).not.toBeInTheDocument();
  251. });
  252. it('should correctly allow deletion of specific analyses', async () => {
  253. const { ui } = getPageObject();
  254. renderProjectActivityAppContainer(
  255. mockComponent({
  256. breadcrumbs: [
  257. { key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project },
  258. ],
  259. configuration: { showHistory: true },
  260. }),
  261. );
  262. await ui.appLoaded();
  263. // Most recent analysis is not deletable.
  264. await ui.openCogMenu('1.1.0.2');
  265. expect(ui.deleteAnalysisBtn.query()).not.toBeInTheDocument();
  266. await ui.deleteAnalysis('1.1.0.1');
  267. expect(screen.queryByText('1.1.0.1')).not.toBeInTheDocument();
  268. });
  269. });
  270. describe('data loading', () => {
  271. function getMock(namespace: string) {
  272. // eslint-disable-next-line jest/no-conditional-in-test
  273. return namespace.includes('.custom') ? 'bugs,code_smells' : GraphType.custom;
  274. }
  275. it('should load all analyses', async () => {
  276. const count = 1000;
  277. projectActivityHandler.setAnalysesList(
  278. times(count, (i) => {
  279. return mockAnalysis({
  280. key: `analysis-${i}`,
  281. date: '2016-01-01T00:00:00+0200',
  282. });
  283. }),
  284. );
  285. const { ui } = getPageObject();
  286. renderProjectActivityAppContainer();
  287. await ui.appLoaded();
  288. expect(ui.activityItem.getAll().length).toBe(count);
  289. });
  290. it('should reload custom graph from local storage', async () => {
  291. jest.mocked(get).mockImplementationOnce(getMock).mockImplementationOnce(getMock);
  292. const { ui } = getPageObject();
  293. renderProjectActivityAppContainer();
  294. await ui.appLoaded();
  295. expect(ui.graphTypeCustom.get()).toBeInTheDocument();
  296. });
  297. it('should correctly fetch the top level component when dealing with sub portfolios', async () => {
  298. const { ui } = getPageObject();
  299. renderProjectActivityAppContainer(
  300. mockComponent({
  301. key: 'unknown',
  302. qualifier: ComponentQualifier.SubPortfolio,
  303. breadcrumbs: [
  304. { key: 'foo', name: 'foo', qualifier: ComponentQualifier.Portfolio },
  305. { key: 'unknown', name: 'unknown', qualifier: ComponentQualifier.SubPortfolio },
  306. ],
  307. }),
  308. );
  309. await ui.appLoaded({ doNotWaitForBranch: true });
  310. // If it didn't fail, it means we correctly queried for project "foo".
  311. expect(ui.activityItem.getAll().length).toBe(4);
  312. });
  313. });
  314. describe('filtering', () => {
  315. it('should correctly filter by event category', async () => {
  316. projectActivityHandler.setAnalysesList([
  317. mockAnalysis({
  318. key: `analysis-1`,
  319. events: [],
  320. }),
  321. mockAnalysis({
  322. key: `analysis-2`,
  323. events: [
  324. mockAnalysisEvent({ key: '1', category: ProjectAnalysisEventCategory.QualityGate }),
  325. ],
  326. }),
  327. mockAnalysis({
  328. key: `analysis-3`,
  329. events: [mockAnalysisEvent({ key: '2', category: ProjectAnalysisEventCategory.Version })],
  330. }),
  331. mockAnalysis({
  332. key: `analysis-4`,
  333. events: [mockAnalysisEvent({ key: '3', category: ProjectAnalysisEventCategory.Version })],
  334. }),
  335. mockAnalysis({
  336. key: `analysis-5`,
  337. events: [mockAnalysisEvent({ key: '4', category: ProjectAnalysisEventCategory.SqUpgrade })],
  338. }),
  339. mockAnalysis({
  340. key: `analysis-6`,
  341. events: [mockAnalysisEvent({ key: '5', category: ProjectAnalysisEventCategory.Version })],
  342. }),
  343. mockAnalysis({
  344. key: `analysis-7`,
  345. events: [mockAnalysisEvent({ key: '6', category: ProjectAnalysisEventCategory.SqUpgrade })],
  346. }),
  347. ]);
  348. const { ui } = getPageObject();
  349. renderProjectActivityAppContainer();
  350. await ui.appLoaded();
  351. await ui.filterByCategory(ProjectAnalysisEventCategory.Version);
  352. expect(ui.activityItem.getAll().length).toBe(3);
  353. await ui.filterByCategory(ProjectAnalysisEventCategory.QualityGate);
  354. expect(ui.activityItem.getAll().length).toBe(1);
  355. await ui.filterByCategory(ProjectAnalysisEventCategory.SqUpgrade);
  356. expect(ui.activityItem.getAll().length).toBe(2);
  357. });
  358. it('should correctly filter by date range', async () => {
  359. projectActivityHandler.setAnalysesList(
  360. times(20, (i) => {
  361. const date = parseDate('2016-01-01T00:00:00.000Z');
  362. date.setDate(date.getDate() + i);
  363. return mockAnalysis({
  364. key: `analysis-${i}`,
  365. date: date.toDateString(),
  366. });
  367. }),
  368. );
  369. const { ui } = getPageObject();
  370. renderProjectActivityAppContainer();
  371. await ui.appLoaded();
  372. expect(ui.activityItem.getAll().length).toBe(20);
  373. await ui.setDateRange('2016-01-10');
  374. expect(ui.activityItem.getAll().length).toBe(11);
  375. await ui.resetDateFilters();
  376. expect(ui.activityItem.getAll().length).toBe(20);
  377. await ui.setDateRange('2016-01-10', '2016-01-11');
  378. expect(ui.activityItem.getAll().length).toBe(2);
  379. await ui.resetDateFilters();
  380. await ui.setDateRange(undefined, '2016-01-08');
  381. expect(ui.activityItem.getAll().length).toBe(8);
  382. });
  383. });
  384. describe('graph interactions', () => {
  385. it('should allow analyses to be clicked to see details for the analysis', async () => {
  386. const { ui } = getPageObject();
  387. renderProjectActivityAppContainer();
  388. await ui.appLoaded();
  389. expect(ui.issuesPopupCell.query()).not.toBeInTheDocument();
  390. await ui.showDetails('1.1.0.1');
  391. expect(ui.issuesPopupCell.get()).toBeInTheDocument();
  392. });
  393. it('should correctly handle customizing the graph', async () => {
  394. const { ui } = getPageObject();
  395. renderProjectActivityAppContainer();
  396. await ui.appLoaded();
  397. await ui.changeGraphType(GraphType.custom);
  398. expect(ui.noDataText.get()).toBeInTheDocument();
  399. // Add metrics.
  400. await ui.openMetricsDropdown();
  401. await ui.toggleMetric(MetricKey.bugs);
  402. await ui.toggleMetric(MetricKey.security_hotspots_reviewed);
  403. await ui.closeMetricsDropdown();
  404. expect(ui.graphs.getAll()).toHaveLength(2);
  405. // Remove metrics.
  406. await ui.openMetricsDropdown();
  407. await ui.toggleMetric(MetricKey.bugs);
  408. await ui.toggleMetric(MetricKey.security_hotspots_reviewed);
  409. await ui.closeMetricsDropdown();
  410. expect(ui.noDataText.get()).toBeInTheDocument();
  411. await ui.changeGraphType(GraphType.issues);
  412. expect(ui.graphs.getAll()).toHaveLength(1);
  413. });
  414. });
  415. function getPageObject() {
  416. const user = userEvent.setup();
  417. const ui = {
  418. // Graph types.
  419. graphTypeSelect: byLabelText('project_activity.graphs.choose_type'),
  420. graphTypeIssues: byText('project_activity.graphs.issues'),
  421. graphTypeCustom: byText('project_activity.graphs.custom'),
  422. // Graphs.
  423. graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }),
  424. noDataText: byText('project_activity.graphs.custom.no_history'),
  425. // Add metrics.
  426. addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
  427. metricCheckbox: (name: MetricKey) => byRole('checkbox', { name }),
  428. // Graph legend.
  429. newCodeLegend: byText('hotspot.filters.period.since_leak_period'),
  430. // Filtering.
  431. categorySelect: byLabelText('project_activity.filter_events'),
  432. resetDatesBtn: byRole('button', { name: 'project_activity.reset_dates' }),
  433. fromDateInput: byLabelText('start_date'),
  434. toDateInput: byLabelText('end_date'),
  435. // Analysis interactions.
  436. activityItem: byLabelText(/project_activity.show_analysis_X_on_graph/),
  437. cogBtn: (id: string) => byRole('button', { name: `project_activity.analysis_X_actions.${id}` }),
  438. seeDetailsBtn: (time: string) =>
  439. byLabelText(`project_activity.show_analysis_X_on_graph.${time}`),
  440. addCustomEventBtn: byRole('menuitem', { name: 'project_activity.add_custom_event' }),
  441. addVersionEvenBtn: byRole('menuitem', { name: 'project_activity.add_version' }),
  442. deleteAnalysisBtn: byRole('menuitem', { name: 'project_activity.delete_analysis' }),
  443. editEventBtn: byRole('button', { name: 'project_activity.events.tooltip.edit' }),
  444. deleteEventBtn: byRole('button', { name: 'project_activity.events.tooltip.delete' }),
  445. // Event modal.
  446. nameInput: byLabelText('name'),
  447. saveBtn: byRole('button', { name: 'save' }),
  448. changeBtn: byRole('button', { name: 'change_verb' }),
  449. deleteBtn: byRole('button', { name: 'delete' }),
  450. // Misc.
  451. loading: byText('loading'),
  452. baseline: byText('project_activity.new_code_period_start'),
  453. issuesPopupCell: byRole('cell', { name: `metric.${MetricKey.violations}.name` }),
  454. monthSelector: byTestId('month-select'),
  455. yearSelector: byTestId('year-select'),
  456. };
  457. return {
  458. user,
  459. ui: {
  460. ...ui,
  461. async appLoaded({ doNotWaitForBranch }: { doNotWaitForBranch?: boolean } = {}) {
  462. expect(await ui.graphs.findAll()).toHaveLength(1);
  463. if (!doNotWaitForBranch) {
  464. await waitFor(() => {
  465. expect(isBranchReady).toBe(true);
  466. });
  467. }
  468. },
  469. async changeGraphType(type: GraphType) {
  470. await user.click(ui.graphTypeSelect.get());
  471. const optionForType = await screen.findByText(`project_activity.graphs.${type}`);
  472. await user.click(optionForType);
  473. },
  474. async openMetricsDropdown() {
  475. await user.click(ui.addMetricBtn.get());
  476. },
  477. async toggleMetric(metric: MetricKey) {
  478. await user.click(ui.metricCheckbox(metric).get());
  479. },
  480. async closeMetricsDropdown() {
  481. await user.keyboard('{Escape}');
  482. },
  483. async openCogMenu(id: string) {
  484. await user.click(ui.cogBtn(id).get());
  485. },
  486. async deleteAnalysis(id: string) {
  487. await user.click(ui.cogBtn(id).get());
  488. await user.click(ui.deleteAnalysisBtn.get());
  489. await user.click(ui.deleteBtn.get());
  490. },
  491. async addVersionEvent(id: string, value: string) {
  492. await user.click(ui.cogBtn(id).get());
  493. await user.click(ui.addVersionEvenBtn.get());
  494. await user.type(ui.nameInput.get(), value);
  495. await user.click(ui.saveBtn.get());
  496. },
  497. async addCustomEvent(id: string, value: string) {
  498. await user.click(ui.cogBtn(id).get());
  499. await user.click(ui.addCustomEventBtn.get());
  500. await user.type(ui.nameInput.get(), value);
  501. await user.click(ui.saveBtn.get());
  502. },
  503. async updateEvent(index: number, value: string) {
  504. await user.click(ui.editEventBtn.getAll()[index]);
  505. await user.clear(ui.nameInput.get());
  506. await user.type(ui.nameInput.get(), value);
  507. await user.click(ui.changeBtn.get());
  508. },
  509. async deleteEvent(index: number) {
  510. await user.click(ui.deleteEventBtn.getAll()[index]);
  511. await user.click(ui.deleteBtn.get());
  512. },
  513. async showDetails(id: string) {
  514. await user.click(ui.seeDetailsBtn(id).get());
  515. },
  516. async filterByCategory(
  517. category: ProjectAnalysisEventCategory | ApplicationAnalysisEventCategory,
  518. ) {
  519. await user.click(ui.categorySelect.get());
  520. const optionForType = await screen.findByText(`event.category.${category}`);
  521. await user.click(optionForType);
  522. },
  523. async setDateRange(from?: string, to?: string) {
  524. if (from) {
  525. await this.selectDate(from, ui.fromDateInput.get());
  526. }
  527. if (to) {
  528. await this.selectDate(to, ui.toDateInput.get());
  529. }
  530. },
  531. async selectDate(date: string, datePickerSelector: HTMLElement) {
  532. const monthMap = [
  533. 'Jan',
  534. 'Feb',
  535. 'Mar',
  536. 'Apr',
  537. 'May',
  538. 'Jun',
  539. 'Jul',
  540. 'Aug',
  541. 'Sep',
  542. 'Oct',
  543. 'Nov',
  544. 'Dec',
  545. ];
  546. const parsedDate = parseDate(date);
  547. await user.click(datePickerSelector);
  548. const monthSelector = within(ui.monthSelector.get()).getByRole('combobox');
  549. await user.click(monthSelector);
  550. const selectedMonthElements = within(ui.monthSelector.get()).getAllByText(
  551. monthMap[parseDate(parsedDate).getMonth()],
  552. );
  553. await user.click(selectedMonthElements[selectedMonthElements.length - 1]);
  554. const yearSelector = within(ui.yearSelector.get()).getByRole('combobox');
  555. await user.click(yearSelector);
  556. const selectedYearElements = within(ui.yearSelector.get()).getAllByText(
  557. parseDate(parsedDate).getFullYear(),
  558. );
  559. await user.click(selectedYearElements[selectedYearElements.length - 1]);
  560. await user.click(
  561. screen.getByText(parseDate(parsedDate).getDate().toString(), { selector: 'button' }),
  562. );
  563. },
  564. async resetDateFilters() {
  565. await user.click(ui.resetDatesBtn.get());
  566. },
  567. },
  568. };
  569. }
  570. function renderProjectActivityAppContainer(
  571. component = mockComponent({
  572. breadcrumbs: [{ key: 'breadcrumb', name: 'breadcrumb', qualifier: ComponentQualifier.Project }],
  573. }),
  574. ) {
  575. return renderAppWithComponentContext(
  576. `project/activity?id=${component.key}`,
  577. () => <Route path="*" element={<ProjectActivityAppContainer />} />,
  578. {
  579. metrics: keyBy(
  580. [
  581. mockMetric({ key: MetricKey.bugs, type: MetricType.Integer }),
  582. mockMetric({ key: MetricKey.code_smells, type: MetricType.Integer }),
  583. mockMetric({ key: MetricKey.security_hotspots_reviewed }),
  584. mockMetric({ key: MetricKey.security_review_rating, type: MetricType.Rating }),
  585. ],
  586. 'key',
  587. ),
  588. },
  589. { component },
  590. );
  591. }