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.

BranchOverview-it.tsx 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  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 { byLabelText, 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. ui.expectSoftwareImpactMeasureCard(
  278. SoftwareQuality.Reliability,
  279. 'A',
  280. {
  281. total: 3,
  282. [SoftwareImpactSeverity.High]: 0,
  283. [SoftwareImpactSeverity.Medium]: 2,
  284. [SoftwareImpactSeverity.Low]: 1,
  285. },
  286. [false, true, false],
  287. undefined,
  288. true,
  289. );
  290. ui.expectSoftwareImpactMeasureCard(
  291. SoftwareQuality.Maintainability,
  292. 'E',
  293. {
  294. total: 2,
  295. [SoftwareImpactSeverity.High]: 0,
  296. [SoftwareImpactSeverity.Medium]: 0,
  297. [SoftwareImpactSeverity.Low]: 1,
  298. },
  299. [false, false, true],
  300. );
  301. });
  302. // eslint-disable-next-line jest/expect-expect
  303. it('should render overall tab without branch specified', async () => {
  304. const { user, ui } = getPageObjects();
  305. renderBranchOverview({ branch: undefined });
  306. await user.click(await ui.overallCodeButton.find());
  307. ui.expectSoftwareImpactMeasureCard(
  308. SoftwareQuality.Maintainability,
  309. 'E',
  310. {
  311. total: 2,
  312. [SoftwareImpactSeverity.High]: 0,
  313. [SoftwareImpactSeverity.Medium]: 0,
  314. [SoftwareImpactSeverity.Low]: 1,
  315. },
  316. [false, false, true],
  317. '',
  318. );
  319. });
  320. it('should render old measures if software impact are missing', async () => {
  321. // Make as if new analysis after upgrade is missing
  322. measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
  323. measuresHandler.deleteComponentMeasure('foo', MetricKey.security_issues);
  324. measuresHandler.deleteComponentMeasure('foo', MetricKey.reliability_issues);
  325. const { user, ui } = getPageObjects();
  326. renderBranchOverview();
  327. await user.click(await ui.overallCodeButton.find());
  328. expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument();
  329. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security);
  330. ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(
  331. SoftwareQuality.Security,
  332. 'B',
  333. 2,
  334. 'VULNERABILITY',
  335. );
  336. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability);
  337. ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(SoftwareQuality.Reliability, 'A', 0, 'BUG');
  338. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability);
  339. ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(
  340. SoftwareQuality.Maintainability,
  341. 'E',
  342. 8,
  343. 'CODE_SMELL',
  344. );
  345. });
  346. it('should render missing software impact measure cards if both software qualities and old measures are missing', async () => {
  347. // Make as if no measures at all
  348. measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
  349. measuresHandler.deleteComponentMeasure('foo', MetricKey.code_smells);
  350. measuresHandler.deleteComponentMeasure('foo', MetricKey.security_issues);
  351. measuresHandler.deleteComponentMeasure('foo', MetricKey.vulnerabilities);
  352. measuresHandler.deleteComponentMeasure('foo', MetricKey.reliability_issues);
  353. measuresHandler.deleteComponentMeasure('foo', MetricKey.bugs);
  354. const { user, ui } = getPageObjects();
  355. renderBranchOverview();
  356. await user.click(await ui.overallCodeButton.find());
  357. expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument();
  358. expect(byText('-', { exact: true }).getAll()).toHaveLength(3);
  359. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security);
  360. expect(
  361. byLabelText(
  362. `overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Security}.B`,
  363. ).get(),
  364. ).toBeInTheDocument();
  365. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability);
  366. expect(
  367. byLabelText(
  368. `overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Reliability}.A`,
  369. ).get(),
  370. ).toBeInTheDocument();
  371. ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability);
  372. expect(
  373. byLabelText(
  374. `overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Maintainability}.E`,
  375. ).get(),
  376. ).toBeInTheDocument();
  377. });
  378. it.each([
  379. ['security_issues', MetricKey.security_issues],
  380. ['reliability_issues', MetricKey.reliability_issues],
  381. ['maintainability_issues', MetricKey.maintainability_issues],
  382. ])(
  383. 'should display info about missing analysis if a project is not computed for %s',
  384. async (missingMetricKey) => {
  385. measuresHandler.deleteComponentMeasure('foo', missingMetricKey as MetricKey);
  386. const { user, ui } = getPageObjects();
  387. renderBranchOverview();
  388. await user.click(await ui.overallCodeButton.find());
  389. expect(
  390. await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find(),
  391. ).toBeInTheDocument();
  392. await user.click(await ui.overallCodeButton.find());
  393. expect(await screen.findByText('overview.missing_project_dataTRK')).toBeInTheDocument();
  394. },
  395. );
  396. });
  397. describe('application overview', () => {
  398. const component = mockComponent({
  399. key: PARENT_COMPONENT_KEY,
  400. name: 'FooApp',
  401. qualifier: ComponentQualifier.Application,
  402. breadcrumbs: [
  403. {
  404. key: PARENT_COMPONENT_KEY,
  405. name: 'FooApp',
  406. qualifier: ComponentQualifier.Application,
  407. },
  408. ],
  409. });
  410. beforeEach(() => {
  411. // We mock this application structure:
  412. // App (foo)
  413. // -- Project 1 (1) - QG OK
  414. // -- Project 2 (2) - QG OK
  415. // -- Project 3 (3) - QG OK
  416. // -- Project 4 (4) - 1 failed condition on new code (new_violations)
  417. const components = Array.from({ length: 4 }).map((_, i) =>
  418. mockComponent({
  419. key: (i + 1).toString(),
  420. name: (i + 1).toString(),
  421. breadcrumbs: [
  422. ...component.breadcrumbs,
  423. {
  424. key: (i + 1).toString(),
  425. name: (i + 1).toString(),
  426. qualifier: ComponentQualifier.Project,
  427. },
  428. ],
  429. }),
  430. );
  431. measuresHandler.setComponents({
  432. component,
  433. ancestors: [],
  434. children: components.map((component) => ({
  435. component,
  436. ancestors: [component],
  437. children: [],
  438. })),
  439. });
  440. const componentMeasures = measuresHandler.getComponentMeasures();
  441. componentMeasures['4'] = {
  442. [MetricKey.new_violations]: mockMeasure({
  443. metric: MetricKey.new_violations,
  444. }),
  445. };
  446. qualityGatesHandler.setApplicationQualityGateStatus({
  447. projects: [
  448. {
  449. key: '1',
  450. name: 'first project',
  451. conditions: [],
  452. caycStatus: CaycStatus.NonCompliant,
  453. status: 'OK',
  454. },
  455. {
  456. key: '2',
  457. name: 'second project',
  458. conditions: [],
  459. caycStatus: CaycStatus.Compliant,
  460. status: 'OK',
  461. },
  462. {
  463. key: '3',
  464. name: 'third project',
  465. conditions: [],
  466. caycStatus: CaycStatus.NonCompliant,
  467. status: 'OK',
  468. },
  469. {
  470. key: '4',
  471. name: 'fourth project',
  472. conditions: [
  473. {
  474. comparator: 'GT',
  475. metric: MetricKey.new_violations,
  476. status: 'ERROR',
  477. value: '3',
  478. errorThreshold: '0',
  479. },
  480. ],
  481. caycStatus: CaycStatus.NonCompliant,
  482. status: 'ERROR',
  483. },
  484. ],
  485. });
  486. });
  487. it('should show failed conditions for child projects', async () => {
  488. renderBranchOverview({ component });
  489. expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument();
  490. expect(
  491. byRole('button', {
  492. name: 'overview.quality_gate.hide_project_conditions_x.fourth project',
  493. }).get(),
  494. ).toBeInTheDocument();
  495. expect(byText('quality_gates.conditions.new_code_1').get()).toBeInTheDocument();
  496. expect(byText('1 metric.new_violations.name').get()).toBeInTheDocument();
  497. });
  498. it("should show projects that don't have a compliant quality gate", async () => {
  499. renderBranchOverview({ component });
  500. expect(
  501. await screen.findByText('overview.quality_gate.application.non_cayc.projects_x.3'),
  502. ).toBeInTheDocument();
  503. expect(screen.getByText('first project')).toBeInTheDocument();
  504. expect(screen.queryByText('second project')).not.toBeInTheDocument();
  505. expect(screen.getByText('third project')).toBeInTheDocument();
  506. });
  507. it('should correctly show an app as empty', async () => {
  508. measuresHandler.registerComponentMeasures({});
  509. renderBranchOverview({ component });
  510. expect(await screen.findByText('portfolio.app.empty')).toBeInTheDocument();
  511. });
  512. it.each([
  513. ['security_issues', MetricKey.security_issues],
  514. ['reliability_issues', MetricKey.reliability_issues],
  515. ['maintainability_issues', MetricKey.maintainability_issues],
  516. ])(
  517. 'should ask to reanalyze all projects if a project is not computed for %s',
  518. async (missingMetricKey) => {
  519. const { ui, user } = getPageObjects();
  520. measuresHandler.deleteComponentMeasure('foo', missingMetricKey as MetricKey);
  521. renderBranchOverview({ component });
  522. await user.click(await ui.overallCodeButton.find());
  523. expect(await screen.findByText('overview.missing_project_dataAPP')).toBeInTheDocument();
  524. },
  525. );
  526. });
  527. it.each([
  528. ['no analysis', [], true],
  529. ['1 analysis, no CI data', [mockAnalysis()], false],
  530. ['1 analysis, no CI detected', [mockAnalysis({ detectedCI: NO_CI_DETECTED })], false],
  531. ['1 analysis, CI detected', [mockAnalysis({ detectedCI: 'Cirrus CI' })], true],
  532. ])(
  533. "should correctly flag a project that wasn't analyzed using a CI (%s)",
  534. async (_, analyses, expected) => {
  535. jest.mocked(getProjectActivity).mockResolvedValueOnce({ analyses, paging: mockPaging() });
  536. renderBranchOverview();
  537. // wait for loading
  538. await screen.findByText('overview.quality_gate.status');
  539. expect(screen.queryByText('overview.project.next_steps.set_up_ci') === null).toBe(expected);
  540. },
  541. );
  542. it.each([
  543. [
  544. 'no upgrade event',
  545. [
  546. mockAnalysis({
  547. events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.Other })],
  548. }),
  549. ],
  550. false,
  551. ],
  552. [
  553. 'upgrade event too old',
  554. [
  555. mockAnalysis({
  556. date: '2023-04-02T12:10:30+0200',
  557. events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
  558. }),
  559. ],
  560. false,
  561. ],
  562. [
  563. 'upgrade event too far down in the list',
  564. [
  565. mockAnalysis({
  566. key: 'a1',
  567. date: '2023-04-13T08:10:30+0200',
  568. }),
  569. mockAnalysis({
  570. key: 'a2',
  571. date: '2023-04-13T09:10:30+0200',
  572. }),
  573. mockAnalysis({
  574. key: 'a3',
  575. date: '2023-04-13T10:10:30+0200',
  576. }),
  577. mockAnalysis({
  578. key: 'a4',
  579. date: '2023-04-13T11:10:30+0200',
  580. }),
  581. mockAnalysis({
  582. key: 'a5',
  583. date: '2023-04-13T12:10:30+0200',
  584. events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
  585. }),
  586. ],
  587. false,
  588. ],
  589. [
  590. 'upgrade event without QP update event',
  591. [
  592. mockAnalysis({
  593. date: '2023-04-13T12:10:30+0200',
  594. events: [mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade })],
  595. }),
  596. ],
  597. false,
  598. ],
  599. [
  600. 'upgrade event with QP update event',
  601. [
  602. mockAnalysis({
  603. date: '2023-04-13T12:10:30+0200',
  604. events: [
  605. mockAnalysisEvent({ category: ProjectAnalysisEventCategory.SqUpgrade }),
  606. mockAnalysisEvent({ category: ProjectAnalysisEventCategory.QualityProfile }),
  607. ],
  608. }),
  609. ],
  610. true,
  611. ],
  612. ])(
  613. 'should correctly display message about SQ upgrade updating QPs',
  614. async (_, analyses, expected) => {
  615. jest.useFakeTimers({
  616. advanceTimers: true,
  617. now: new Date('2023-04-25T12:00:00+0200'),
  618. });
  619. jest.mocked(getProjectActivity).mockResolvedValueOnce({
  620. analyses,
  621. paging: mockPaging(),
  622. });
  623. const { user, ui } = getPageObjects();
  624. renderBranchOverview();
  625. await user.click(await ui.overallCodeButton.find());
  626. expect(await byText('overview.quality_gate.status').find()).toBeInTheDocument();
  627. await waitFor(() =>
  628. expect(
  629. screen.queryByText(/overview.quality_profiles_update_after_sq_upgrade.message/) !== null,
  630. ).toBe(expected),
  631. );
  632. jest.useRealTimers();
  633. },
  634. );
  635. function renderBranchOverview(props: Partial<BranchOverview['props']> = {}) {
  636. return renderComponent(
  637. <CurrentUserContextProvider currentUser={mockLoggedInUser()}>
  638. <BranchOverview
  639. branch={mockMainBranch()}
  640. component={mockComponent({
  641. breadcrumbs: [mockComponent({ key: 'foo' })],
  642. key: 'foo',
  643. name: 'Foo',
  644. version: 'version-1.0',
  645. })}
  646. {...props}
  647. />
  648. </CurrentUserContextProvider>,
  649. );
  650. }