Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

BranchOverview-it.tsx 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  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 } from '@testing-library/react';
  21. import * as React from 'react';
  22. import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock';
  23. import ApplicationServiceMock from '../../../../api/mocks/ApplicationServiceMock';
  24. import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
  25. import { MeasuresServiceMock } from '../../../../api/mocks/MeasuresServiceMock';
  26. import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
  27. import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock';
  28. import { TimeMachineServiceMock } from '../../../../api/mocks/TimeMachineServiceMock';
  29. import { PARENT_COMPONENT_KEY } from '../../../../api/mocks/data/ids';
  30. import { getProjectActivity } from '../../../../api/projectActivity';
  31. import { getQualityGateProjectStatus } from '../../../../api/quality-gates';
  32. import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider';
  33. import { parseDate } from '../../../../helpers/dates';
  34. import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
  35. import { mockComponent } from '../../../../helpers/mocks/component';
  36. import { mockAnalysis, mockAnalysisEvent } from '../../../../helpers/mocks/project-activity';
  37. import { mockQualityGateProjectStatus } from '../../../../helpers/mocks/quality-gates';
  38. import { mockLoggedInUser, mockMeasure, mockPaging } from '../../../../helpers/testMocks';
  39. import { renderComponent } from '../../../../helpers/testReactTestingUtils';
  40. import { byRole, byText } from '../../../../helpers/testSelector';
  41. import { SoftwareImpactSeverity, SoftwareQuality } from '../../../../types/clean-code-taxonomy';
  42. import { ComponentQualifier } from '../../../../types/component';
  43. import { MetricKey } from '../../../../types/metrics';
  44. import { ProjectAnalysisEventCategory } from '../../../../types/project-activity';
  45. import { CaycStatus } from '../../../../types/types';
  46. import BranchOverview, { NO_CI_DETECTED } from '../BranchOverview';
  47. import { getPageObjects } from '../test-utils';
  48. const almHandler = new AlmSettingsServiceMock();
  49. let branchesHandler: BranchesServiceMock;
  50. let measuresHandler: MeasuresServiceMock;
  51. let applicationHandler: ApplicationServiceMock;
  52. let projectActivityHandler: ProjectActivityServiceMock;
  53. let timeMarchineHandler: TimeMachineServiceMock;
  54. let qualityGatesHandler: QualityGatesServiceMock;
  55. beforeAll(() => {
  56. branchesHandler = new BranchesServiceMock();
  57. measuresHandler = new MeasuresServiceMock();
  58. applicationHandler = new ApplicationServiceMock();
  59. projectActivityHandler = new ProjectActivityServiceMock();
  60. projectActivityHandler.setAnalysesList([
  61. mockAnalysis({ key: 'a1', detectedCI: 'Cirrus CI' }),
  62. mockAnalysis({ key: 'a2' }),
  63. mockAnalysis({ key: 'a3' }),
  64. mockAnalysis({ key: 'a4' }),
  65. mockAnalysis({ key: 'a5' }),
  66. ]);
  67. timeMarchineHandler = new TimeMachineServiceMock();
  68. timeMarchineHandler.setMeasureHistory([
  69. { metric: MetricKey.bugs, history: [{ date: parseDate('2019-01-05'), value: '2.0' }] },
  70. { metric: MetricKey.vulnerabilities, history: [{ date: parseDate('2019-01-05'), value: '0' }] },
  71. { metric: MetricKey.sqale_index, history: [{ date: parseDate('2019-01-01'), value: '1.0' }] },
  72. {
  73. metric: MetricKey.duplicated_lines_density,
  74. history: [{ date: parseDate('2019-01-02'), value: '1.0' }],
  75. },
  76. { metric: MetricKey.ncloc, history: [{ date: parseDate('2019-01-03'), value: '10000' }] },
  77. { metric: MetricKey.coverage, history: [{ date: parseDate('2019-01-04'), value: '95.5' }] },
  78. ]);
  79. qualityGatesHandler = new QualityGatesServiceMock();
  80. qualityGatesHandler.setQualityGateProjectStatus({
  81. status: 'ERROR',
  82. conditions: [
  83. {
  84. actualValue: '2',
  85. comparator: 'GT',
  86. errorThreshold: '1',
  87. metricKey: MetricKey.new_reliability_rating,
  88. periodIndex: 1,
  89. status: 'ERROR',
  90. },
  91. {
  92. actualValue: '5',
  93. comparator: 'GT',
  94. errorThreshold: '2.0',
  95. metricKey: MetricKey.bugs,
  96. periodIndex: 0,
  97. status: 'ERROR',
  98. },
  99. {
  100. actualValue: '2',
  101. comparator: 'GT',
  102. errorThreshold: '1.0',
  103. metricKey: 'unknown_metric',
  104. periodIndex: 0,
  105. status: 'ERROR',
  106. },
  107. ],
  108. });
  109. });
  110. afterEach(() => {
  111. jest.clearAllMocks();
  112. branchesHandler.reset();
  113. measuresHandler.reset();
  114. applicationHandler.reset();
  115. projectActivityHandler.reset();
  116. timeMarchineHandler.reset();
  117. qualityGatesHandler.reset();
  118. almHandler.reset();
  119. });
  120. describe('project overview', () => {
  121. it('should render a successful quality gate', async () => {
  122. qualityGatesHandler.setQualityGateProjectStatus(
  123. mockQualityGateProjectStatus({
  124. status: 'OK',
  125. }),
  126. );
  127. const { user } = getPageObjects();
  128. renderBranchOverview();
  129. // Meta info
  130. expect(await screen.findByText('master')).toBeInTheDocument();
  131. expect(screen.getByText('version-1.0')).toBeInTheDocument();
  132. // QG panel
  133. expect(screen.getByText('metric.level.OK')).toBeInTheDocument();
  134. expect(screen.getByText('overview.passed.clean_code')).toBeInTheDocument();
  135. expect(
  136. screen.queryByText('overview.quality_gate.conditions.cayc.warning'),
  137. ).not.toBeInTheDocument();
  138. // Measures panel
  139. expect(screen.getByText('overview.new_issues')).toBeInTheDocument();
  140. expect(
  141. byRole('link', {
  142. name: 'overview.see_more_details_on_x_of_y.1.metric.new_accepted_issues.name',
  143. }).get(),
  144. ).toBeInTheDocument();
  145. expect(byText('overview.accepted_issues.help').get()).toBeVisible();
  146. await user.click(byRole('tab', { name: 'overview.overall_code' }).get());
  147. expect(byText('overview.accepted_issues.help').get()).toBeVisible();
  148. });
  149. it('should show a successful non-compliant QG', async () => {
  150. jest
  151. .mocked(getQualityGateProjectStatus)
  152. .mockResolvedValueOnce(
  153. mockQualityGateProjectStatus({ status: 'OK', caycStatus: CaycStatus.NonCompliant }),
  154. );
  155. renderBranchOverview();
  156. expect(await screen.findByText('metric.level.OK')).toBeInTheDocument();
  157. expect(
  158. screen.queryByText('overview.quality_gate.conditions.cayc.warning'),
  159. ).not.toBeInTheDocument();
  160. });
  161. it('should show a successful non-compliant QG as admin', async () => {
  162. jest
  163. .mocked(getQualityGateProjectStatus)
  164. .mockResolvedValueOnce(
  165. mockQualityGateProjectStatus({ status: 'OK', caycStatus: CaycStatus.NonCompliant }),
  166. );
  167. qualityGatesHandler.setIsAdmin(true);
  168. qualityGatesHandler.setGetGateForProjectName('Non Cayc QG');
  169. renderBranchOverview();
  170. await screen.findByText('metric.level.OK');
  171. expect(
  172. await screen.findByText('overview.quality_gate.conditions.cayc.warning'),
  173. ).toBeInTheDocument();
  174. });
  175. it('should show a failed QG', async () => {
  176. qualityGatesHandler.setQualityGateProjectStatus(
  177. mockQualityGateProjectStatus({
  178. status: 'ERROR',
  179. conditions: [
  180. {
  181. actualValue: '2',
  182. comparator: 'GT',
  183. errorThreshold: '1',
  184. metricKey: MetricKey.new_reliability_rating,
  185. periodIndex: 1,
  186. status: 'ERROR',
  187. },
  188. {
  189. actualValue: '5',
  190. comparator: 'GT',
  191. errorThreshold: '2.0',
  192. metricKey: MetricKey.bugs,
  193. periodIndex: 0,
  194. status: 'ERROR',
  195. },
  196. {
  197. actualValue: '10',
  198. comparator: 'PT',
  199. errorThreshold: '85',
  200. metricKey: MetricKey.new_coverage,
  201. periodIndex: 0,
  202. status: 'ERROR',
  203. },
  204. {
  205. actualValue: '5',
  206. comparator: 'GT',
  207. errorThreshold: '2.0',
  208. metricKey: MetricKey.new_security_hotspots_reviewed,
  209. periodIndex: 0,
  210. status: 'ERROR',
  211. },
  212. {
  213. actualValue: '5',
  214. comparator: 'GT',
  215. errorThreshold: '2.0',
  216. metricKey: MetricKey.new_violations,
  217. periodIndex: 0,
  218. status: 'ERROR',
  219. },
  220. {
  221. actualValue: '2',
  222. comparator: 'GT',
  223. errorThreshold: '1.0',
  224. metricKey: 'unknown_metric',
  225. periodIndex: 0,
  226. status: 'ERROR',
  227. },
  228. ],
  229. }),
  230. );
  231. renderBranchOverview();
  232. expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument();
  233. expect(screen.getByText(/overview.X_conditions_failed/)).toBeInTheDocument();
  234. expect(screen.getAllByText(/overview.quality_gate.required_x/)).toHaveLength(3);
  235. expect(
  236. screen.getByRole('link', {
  237. name: '1 1 metric.new_security_hotspots_reviewed.name quality_gates.operator.GT 2',
  238. }),
  239. ).toHaveAttribute('href', '/security_hotspots?id=foo&inNewCodePeriod=true');
  240. });
  241. it('should correctly show a project as empty', async () => {
  242. measuresHandler.registerComponentMeasures({});
  243. renderBranchOverview();
  244. expect(await screen.findByText('overview.project.main_branch_empty')).toBeInTheDocument();
  245. });
  246. // eslint-disable-next-line jest/expect-expect
  247. it('should render software impact measure cards', async () => {
  248. qualityGatesHandler.setQualityGateProjectStatus(
  249. mockQualityGateProjectStatus({
  250. status: 'ERROR',
  251. conditions: [
  252. {
  253. actualValue: '2',
  254. comparator: 'GT',
  255. errorThreshold: '1',
  256. metricKey: MetricKey.reliability_rating,
  257. periodIndex: 1,
  258. status: 'ERROR',
  259. },
  260. ],
  261. }),
  262. );
  263. const { user, ui } = getPageObjects();
  264. renderBranchOverview();
  265. await user.click(await ui.overallCodeButton.find());
  266. ui.expectSoftwareImpactMeasureCard(
  267. SoftwareQuality.Security,
  268. 'B',
  269. {
  270. total: 1,
  271. [SoftwareImpactSeverity.High]: 0,
  272. [SoftwareImpactSeverity.Medium]: 1,
  273. [SoftwareImpactSeverity.Low]: 0,
  274. },
  275. [false, true, false],
  276. );
  277. await ui.expectSoftwareImpactMeasureCardRatingTooltip(
  278. SoftwareQuality.Security,
  279. 'B',
  280. 'overview.measures.software_impact.improve_rating_tooltip.software_quality.SECURITY.software_quality.security.B.overview.measures.software_impact.severity.LOW.improve_tooltip',
  281. );
  282. ui.expectSoftwareImpactMeasureCard(
  283. SoftwareQuality.Reliability,
  284. 'A',
  285. {
  286. total: 3,
  287. [SoftwareImpactSeverity.High]: 0,
  288. [SoftwareImpactSeverity.Medium]: 2,
  289. [SoftwareImpactSeverity.Low]: 1,
  290. },
  291. [false, true, false],
  292. undefined,
  293. true,
  294. );
  295. await ui.expectSoftwareImpactMeasureCardRatingTooltip(
  296. SoftwareQuality.Reliability,
  297. 'A',
  298. 'overview.measures.software_impact.improve_rating_tooltip.A.software_quality.RELIABILITY.software_quality.reliability.A.overview.measures.software_impact.severity.LOW.improve_tooltip',
  299. );
  300. ui.expectSoftwareImpactMeasureCard(
  301. SoftwareQuality.Maintainability,
  302. 'E',
  303. {
  304. total: 2,
  305. [SoftwareImpactSeverity.High]: 0,
  306. [SoftwareImpactSeverity.Medium]: 0,
  307. [SoftwareImpactSeverity.Low]: 1,
  308. },
  309. [false, false, true],
  310. );
  311. await ui.expectSoftwareImpactMeasureCardRatingTooltip(
  312. SoftwareQuality.Maintainability,
  313. 'E',
  314. 'overview.measures.software_impact.improve_rating_tooltip.MAINTAINABILITY.software_quality.MAINTAINABILITY.software_quality.maintainability.E.overview.measures.software_impact.severity.HIGH.improve_tooltip',
  315. );
  316. });
  317. // eslint-disable-next-line jest/expect-expect
  318. it('should render overall tab without branch specified', async () => {
  319. const { user, ui } = getPageObjects();
  320. renderBranchOverview({ branch: undefined });
  321. await user.click(await ui.overallCodeButton.find());
  322. ui.expectSoftwareImpactMeasureCard(
  323. SoftwareQuality.Maintainability,
  324. 'E',
  325. {
  326. total: 2,
  327. [SoftwareImpactSeverity.High]: 0,
  328. [SoftwareImpactSeverity.Medium]: 0,
  329. [SoftwareImpactSeverity.Low]: 1,
  330. },
  331. [false, false, true],
  332. '',
  333. );
  334. });
  335. it('should render old measures if software impact are missing', async () => {
  336. // Make as if new analysis after upgrade is missing
  337. measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
  338. measuresHandler.deleteComponentMeasure('foo', MetricKey.security_issues);
  339. measuresHandler.deleteComponentMeasure('foo', MetricKey.reliability_issues);
  340. const { user, ui } = getPageObjects();
  341. renderBranchOverview();
  342. await user.click(await ui.overallCodeButton.find());
  343. expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument();
  344. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security);
  345. ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(
  346. SoftwareQuality.Security,
  347. 'B',
  348. 2,
  349. 'VULNERABILITY',
  350. );
  351. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability);
  352. ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(SoftwareQuality.Reliability, 'A', 0, 'BUG');
  353. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability);
  354. ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(
  355. SoftwareQuality.Maintainability,
  356. 'E',
  357. 8,
  358. 'CODE_SMELL',
  359. );
  360. });
  361. it('should render missing software impact measure cards if both software qualities and old measures are missing', async () => {
  362. // Make as if no measures at all
  363. measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
  364. measuresHandler.deleteComponentMeasure('foo', MetricKey.code_smells);
  365. measuresHandler.deleteComponentMeasure('foo', MetricKey.security_issues);
  366. measuresHandler.deleteComponentMeasure('foo', MetricKey.vulnerabilities);
  367. measuresHandler.deleteComponentMeasure('foo', MetricKey.reliability_issues);
  368. measuresHandler.deleteComponentMeasure('foo', MetricKey.bugs);
  369. const { user, ui } = getPageObjects();
  370. renderBranchOverview();
  371. await user.click(await ui.overallCodeButton.find());
  372. expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument();
  373. expect(byText('-', { exact: true }).getAll()).toHaveLength(3);
  374. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security);
  375. expect(
  376. ui.softwareImpactMeasureCardRating(SoftwareQuality.Security, 'B').get(),
  377. ).toBeInTheDocument();
  378. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability);
  379. expect(
  380. ui.softwareImpactMeasureCardRating(SoftwareQuality.Reliability, 'A').get(),
  381. ).toBeInTheDocument();
  382. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability);
  383. expect(
  384. ui.softwareImpactMeasureCardRating(SoftwareQuality.Maintainability, 'E').get(),
  385. ).toBeInTheDocument();
  386. });
  387. it.each([
  388. ['security_issues', MetricKey.security_issues],
  389. ['reliability_issues', MetricKey.reliability_issues],
  390. ['maintainability_issues', MetricKey.maintainability_issues],
  391. ])(
  392. 'should display info about missing analysis if a project is not computed for %s',
  393. async (missingMetricKey) => {
  394. measuresHandler.deleteComponentMeasure('foo', missingMetricKey as MetricKey);
  395. const { user, ui } = getPageObjects();
  396. renderBranchOverview();
  397. await user.click(await ui.overallCodeButton.find());
  398. expect(
  399. await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find(),
  400. ).toBeInTheDocument();
  401. await user.click(await ui.overallCodeButton.find());
  402. expect(await screen.findByText('overview.missing_project_dataTRK')).toBeInTheDocument();
  403. },
  404. );
  405. });
  406. describe('application overview', () => {
  407. const component = mockComponent({
  408. key: PARENT_COMPONENT_KEY,
  409. name: 'FooApp',
  410. qualifier: ComponentQualifier.Application,
  411. breadcrumbs: [
  412. {
  413. key: PARENT_COMPONENT_KEY,
  414. name: 'FooApp',
  415. qualifier: ComponentQualifier.Application,
  416. },
  417. ],
  418. });
  419. beforeEach(() => {
  420. // We mock this application structure:
  421. // App (foo)
  422. // -- Project 1 (1) - QG OK
  423. // -- Project 2 (2) - QG OK
  424. // -- Project 3 (3) - QG OK
  425. // -- Project 4 (4) - 1 failed condition on new code (new_violations)
  426. const components = Array.from({ length: 4 }).map((_, i) =>
  427. mockComponent({
  428. key: (i + 1).toString(),
  429. name: (i + 1).toString(),
  430. breadcrumbs: [
  431. ...component.breadcrumbs,
  432. {
  433. key: (i + 1).toString(),
  434. name: (i + 1).toString(),
  435. qualifier: ComponentQualifier.Project,
  436. },
  437. ],
  438. }),
  439. );
  440. measuresHandler.setComponents({
  441. component,
  442. ancestors: [],
  443. children: components.map((component) => ({
  444. component,
  445. ancestors: [component],
  446. children: [],
  447. })),
  448. });
  449. const componentMeasures = measuresHandler.getComponentMeasures();
  450. componentMeasures['4'] = {
  451. [MetricKey.new_violations]: mockMeasure({
  452. metric: MetricKey.new_violations,
  453. }),
  454. };
  455. qualityGatesHandler.setApplicationQualityGateStatus({
  456. projects: [
  457. {
  458. key: '1',
  459. name: 'first project',
  460. conditions: [],
  461. caycStatus: CaycStatus.NonCompliant,
  462. status: 'OK',
  463. },
  464. {
  465. key: '2',
  466. name: 'second project',
  467. conditions: [],
  468. caycStatus: CaycStatus.Compliant,
  469. status: 'OK',
  470. },
  471. {
  472. key: '3',
  473. name: 'third project',
  474. conditions: [],
  475. caycStatus: CaycStatus.NonCompliant,
  476. status: 'OK',
  477. },
  478. {
  479. key: '4',
  480. name: 'fourth project',
  481. conditions: [
  482. {
  483. comparator: 'GT',
  484. metric: MetricKey.new_violations,
  485. status: 'ERROR',
  486. value: '3',
  487. errorThreshold: '0',
  488. },
  489. ],
  490. caycStatus: CaycStatus.NonCompliant,
  491. status: 'ERROR',
  492. },
  493. ],
  494. });
  495. });
  496. it('should show failed conditions for child projects', async () => {
  497. renderBranchOverview({ component });
  498. expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument();
  499. expect(
  500. byRole('button', {
  501. name: 'overview.quality_gate.hide_project_conditions_x.fourth project',
  502. }).get(),
  503. ).toBeInTheDocument();
  504. expect(byText('quality_gates.conditions.new_code_1').get()).toBeInTheDocument();
  505. expect(byText('1 metric.new_violations.name').get()).toBeInTheDocument();
  506. });
  507. it("should show projects that don't have a compliant quality gate", async () => {
  508. renderBranchOverview({ component });
  509. expect(
  510. await screen.findByText('overview.quality_gate.application.non_cayc.projects_x.3'),
  511. ).toBeInTheDocument();
  512. expect(screen.getByText('first project')).toBeInTheDocument();
  513. expect(screen.queryByText('second project')).not.toBeInTheDocument();
  514. expect(screen.getByText('third project')).toBeInTheDocument();
  515. });
  516. it('should correctly show an app as empty', async () => {
  517. measuresHandler.registerComponentMeasures({});
  518. renderBranchOverview({ component });
  519. expect(await screen.findByText('portfolio.app.empty')).toBeInTheDocument();
  520. });
  521. it.each([
  522. ['security_issues', MetricKey.security_issues],
  523. ['reliability_issues', MetricKey.reliability_issues],
  524. ['maintainability_issues', MetricKey.maintainability_issues],
  525. ])(
  526. 'should ask to reanalyze all projects if a project is not computed for %s',
  527. async (missingMetricKey) => {
  528. const { ui, user } = getPageObjects();
  529. measuresHandler.deleteComponentMeasure('foo', missingMetricKey as MetricKey);
  530. renderBranchOverview({ component });
  531. await user.click(await ui.overallCodeButton.find());
  532. expect(await screen.findByText('overview.missing_project_dataAPP')).toBeInTheDocument();
  533. },
  534. );
  535. });
  536. it.each([
  537. ['no analysis', [], true],
  538. ['1 analysis, no CI data', [mockAnalysis()], false],
  539. ['1 analysis, no CI detected', [mockAnalysis({ detectedCI: NO_CI_DETECTED })], false],
  540. ['1 analysis, CI detected', [mockAnalysis({ detectedCI: 'Cirrus CI' })], true],
  541. ])(
  542. "should correctly flag a project that wasn't analyzed using a CI (%s)",
  543. async (_, analyses, expected) => {
  544. jest.mocked(getProjectActivity).mockResolvedValueOnce({ analyses, paging: mockPaging() });
  545. renderBranchOverview();
  546. // wait for loading
  547. await screen.findByText('overview.quality_gate.status');
  548. expect(screen.queryByText('overview.project.next_steps.set_up_ci') === null).toBe(expected);
  549. },
  550. );
  551. it.each([
  552. [
  553. 'no upgrade event',
  554. [
  555. mockAnalysis({
  556. events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.Other })],
  557. }),
  558. ],
  559. false,
  560. ],
  561. [
  562. 'upgrade event too old',
  563. [
  564. mockAnalysis({
  565. date: '2023-04-02T12:10:30+0200',
  566. events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
  567. }),
  568. ],
  569. false,
  570. ],
  571. [
  572. 'upgrade event too far down in the list',
  573. [
  574. mockAnalysis({
  575. key: 'a1',
  576. date: '2023-04-13T08:10:30+0200',
  577. }),
  578. mockAnalysis({
  579. key: 'a2',
  580. date: '2023-04-13T09:10:30+0200',
  581. }),
  582. mockAnalysis({
  583. key: 'a3',
  584. date: '2023-04-13T10:10:30+0200',
  585. }),
  586. mockAnalysis({
  587. key: 'a4',
  588. date: '2023-04-13T11:10:30+0200',
  589. }),
  590. mockAnalysis({
  591. key: 'a5',
  592. date: '2023-04-13T12:10:30+0200',
  593. events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
  594. }),
  595. ],
  596. false,
  597. ],
  598. [
  599. 'upgrade event without QP update event',
  600. [
  601. mockAnalysis({
  602. date: '2023-04-13T12:10:30+0200',
  603. events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
  604. }),
  605. ],
  606. false,
  607. ],
  608. [
  609. 'upgrade event with QP update event',
  610. [
  611. mockAnalysis({
  612. date: '2023-04-13T12:10:30+0200',
  613. events: [
  614. mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade }),
  615. mockAnalysisEvent({ category: ProjectAnalysisEventCategory.QualityProfile }),
  616. ],
  617. }),
  618. ],
  619. true,
  620. ],
  621. ])(
  622. 'should correctly display message about SQ upgrade updating QPs',
  623. async (_, analyses, expected) => {
  624. jest.useFakeTimers({
  625. advanceTimers: true,
  626. now: new Date('2023-04-25T12:00:00+0200'),
  627. });
  628. jest.mocked(getProjectActivity).mockResolvedValueOnce({
  629. analyses,
  630. paging: mockPaging(),
  631. });
  632. const { user, ui } = getPageObjects();
  633. renderBranchOverview();
  634. await user.click(await ui.overallCodeButton.find());
  635. expect(await byText('overview.quality_gate.status').find()).toBeInTheDocument();
  636. await waitFor(() =>
  637. expect(
  638. screen.queryByText(/overview.quality_profiles_update_after_sq_upgrade.message/) !== null,
  639. ).toBe(expected),
  640. );
  641. jest.useRealTimers();
  642. },
  643. );
  644. function renderBranchOverview(props: Partial<BranchOverview['props']> = {}) {
  645. return renderComponent(
  646. <CurrentUserContextProvider currentUser={mockLoggedInUser()}>
  647. <BranchOverview
  648. branch={mockMainBranch()}
  649. component={mockComponent({
  650. breadcrumbs: [mockComponent({ key: 'foo' })],
  651. key: 'foo',
  652. name: 'Foo',
  653. version: 'version-1.0',
  654. })}
  655. {...props}
  656. />
  657. </CurrentUserContextProvider>,
  658. );
  659. }