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.

PullRequestOverview-it.tsx 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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 userEvent from '@testing-library/user-event';
  22. import * as React from 'react';
  23. import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
  24. import { fetchQualityGate, getQualityGateProjectStatus } from '../../../../api/quality-gates';
  25. import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider';
  26. import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
  27. import { mockComponent } from '../../../../helpers/mocks/component';
  28. import {
  29. mockQualityGate,
  30. mockQualityGateProjectCondition,
  31. } from '../../../../helpers/mocks/quality-gates';
  32. import { mockLoggedInUser, mockMeasure, mockMetric } from '../../../../helpers/testMocks';
  33. import { renderComponent } from '../../../../helpers/testReactTestingUtils';
  34. import { byLabelText, byRole } from '../../../../helpers/testSelector';
  35. import { ComponentPropsType } from '../../../../helpers/testUtils';
  36. import { ComponentQualifier } from '../../../../types/component';
  37. import { MetricKey, MetricType } from '../../../../types/metrics';
  38. import { CaycStatus } from '../../../../types/types';
  39. import { NoticeType } from '../../../../types/users';
  40. import PullRequestOverview from '../PullRequestOverview';
  41. jest.mock('../../../../api/measures', () => {
  42. return {
  43. ...jest.requireActual('../../../../types/metrics'),
  44. getMeasuresWithMetrics: jest.fn().mockResolvedValue({
  45. component: {
  46. key: '',
  47. name: '',
  48. qualifier: ComponentQualifier.Project,
  49. measures: [
  50. mockMeasure({
  51. metric: MetricKey.new_coverage,
  52. }),
  53. mockMeasure({
  54. metric: MetricKey.duplicated_lines,
  55. }),
  56. mockMeasure({
  57. metric: MetricKey.new_bugs,
  58. }),
  59. mockMeasure({
  60. metric: MetricKey.new_lines,
  61. }),
  62. mockMeasure({
  63. metric: MetricKey.new_violations,
  64. }),
  65. mockMeasure({
  66. metric: MetricKey.pull_request_fixed_issues,
  67. }),
  68. ],
  69. },
  70. metrics: [
  71. mockMetric({ key: MetricKey.new_coverage }),
  72. mockMetric({ key: MetricKey.duplicated_lines }),
  73. mockMetric({ key: MetricKey.new_lines, type: MetricType.ShortInteger }),
  74. mockMetric({ key: MetricKey.new_bugs, type: MetricType.Integer }),
  75. mockMetric({ key: MetricKey.new_violations }),
  76. mockMetric({ key: MetricKey.pull_request_fixed_issues }),
  77. ],
  78. }),
  79. };
  80. });
  81. jest.mock('../../../../api/quality-gates', () => {
  82. const { mockQualityGateProjectStatus, mockQualityGateApplicationStatus, mockQualityGate } =
  83. jest.requireActual('../../../../helpers/mocks/quality-gates');
  84. const { MetricKey } = jest.requireActual('../../../../types/metrics');
  85. return {
  86. getQualityGateProjectStatus: jest.fn().mockResolvedValue(
  87. mockQualityGateProjectStatus({
  88. status: 'ERROR',
  89. conditions: [
  90. {
  91. actualValue: '2',
  92. comparator: 'GT',
  93. errorThreshold: '1',
  94. metricKey: MetricKey.new_reliability_rating,
  95. periodIndex: 1,
  96. status: 'ERROR',
  97. },
  98. {
  99. actualValue: '5',
  100. comparator: 'GT',
  101. errorThreshold: '2.0',
  102. metricKey: MetricKey.bugs,
  103. periodIndex: 0,
  104. status: 'ERROR',
  105. },
  106. {
  107. actualValue: '2',
  108. comparator: 'GT',
  109. errorThreshold: '1.0',
  110. metricKey: 'unknown_metric',
  111. periodIndex: 0,
  112. status: 'ERROR',
  113. },
  114. ],
  115. }),
  116. ),
  117. getApplicationQualityGate: jest.fn().mockResolvedValue(mockQualityGateApplicationStatus()),
  118. getGateForProject: jest.fn().mockResolvedValue(mockQualityGate({ isBuiltIn: true })),
  119. fetchQualityGate: jest.fn().mockResolvedValue(mockQualityGate({ isBuiltIn: true })),
  120. };
  121. });
  122. const branchesHandler = new BranchesServiceMock();
  123. afterEach(() => {
  124. branchesHandler.reset();
  125. });
  126. it('should render links correctly', async () => {
  127. jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
  128. status: 'OK',
  129. conditions: [],
  130. caycStatus: CaycStatus.Compliant,
  131. ignoredConditions: false,
  132. });
  133. renderPullRequestOverview();
  134. await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument());
  135. expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument();
  136. expect(
  137. byRole('link', {
  138. name: 'overview.see_more_details_on_x_of_y.1.metric.new_violations.name',
  139. }).get(),
  140. ).toHaveAttribute(
  141. 'href',
  142. '/project/issues?pullRequest=1001&issueStatuses=OPEN%2CCONFIRMED&id=foo',
  143. );
  144. expect(
  145. byRole('link', {
  146. name: 'overview.see_more_details_on_x_of_y.1.metric.pull_request_fixed_issues.name',
  147. }).get(),
  148. ).toHaveAttribute('href', '/project/issues?fixedInPullRequest=1001&id=foo');
  149. expect(
  150. screen.getByRole('link', { name: 'no_measure_value_x.metric.new_security_hotspots.name' }),
  151. ).toBeInTheDocument();
  152. expect(
  153. screen.getByRole('link', { name: 'no_measure_value_x.metric.new_accepted_issues.name' }),
  154. ).toBeInTheDocument();
  155. expect(
  156. screen.getByRole('link', {
  157. name: 'no_measure_value_x.metric.new_duplicated_lines_density.name',
  158. }),
  159. ).toBeInTheDocument();
  160. });
  161. it('should render correctly for a passed QG', async () => {
  162. jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
  163. status: 'OK',
  164. conditions: [],
  165. caycStatus: CaycStatus.Compliant,
  166. ignoredConditions: false,
  167. });
  168. renderPullRequestOverview();
  169. await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument());
  170. expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument();
  171. expect(screen.getByText('metric.new_lines.name')).toBeInTheDocument();
  172. expect(screen.getByText(/overview.last_analysis_x/)).toBeInTheDocument();
  173. });
  174. it('should render correctly if conditions are ignored', async () => {
  175. jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
  176. status: 'OK',
  177. conditions: [],
  178. caycStatus: CaycStatus.Compliant,
  179. ignoredConditions: true,
  180. });
  181. renderPullRequestOverview();
  182. await waitFor(async () =>
  183. expect(await screen.findByText('overview.quality_gate.ignored_conditions')).toBeInTheDocument(),
  184. );
  185. });
  186. it('should render correctly for a failed QG', async () => {
  187. jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
  188. status: 'ERROR',
  189. conditions: [
  190. mockQualityGateProjectCondition({
  191. errorThreshold: '2.0',
  192. metricKey: MetricKey.new_coverage,
  193. periodIndex: 1,
  194. }),
  195. mockQualityGateProjectCondition({
  196. errorThreshold: '1.0',
  197. metricKey: MetricKey.duplicated_lines,
  198. periodIndex: 1,
  199. }),
  200. mockQualityGateProjectCondition({
  201. errorThreshold: '3',
  202. metricKey: MetricKey.new_bugs,
  203. periodIndex: 1,
  204. }),
  205. ],
  206. caycStatus: CaycStatus.Compliant,
  207. ignoredConditions: true,
  208. });
  209. renderPullRequestOverview();
  210. await waitFor(async () =>
  211. expect(
  212. await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(),
  213. ).toBeInTheDocument(),
  214. );
  215. expect(
  216. byRole('link', {
  217. name: 'overview.measures.failed_badge overview.failed_condition.x_required 10.0% duplicated_lines≤ 1.0%',
  218. }).get(),
  219. ).toBeInTheDocument();
  220. expect(
  221. byRole('link', {
  222. name: 'overview.measures.failed_badge overview.failed_condition.x_required 10 new_bugs≤ 3',
  223. }).get(),
  224. ).toBeInTheDocument();
  225. });
  226. it('renders SL promotion', async () => {
  227. const user = userEvent.setup();
  228. jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
  229. status: 'ERROR',
  230. conditions: [
  231. mockQualityGateProjectCondition({
  232. errorThreshold: '2.0',
  233. metricKey: MetricKey.new_coverage,
  234. periodIndex: 1,
  235. }),
  236. ],
  237. caycStatus: CaycStatus.Compliant,
  238. ignoredConditions: true,
  239. });
  240. renderPullRequestOverview();
  241. await waitFor(async () =>
  242. expect(
  243. await byRole('heading', { name: 'overview.sonarlint_ad.header' }).find(),
  244. ).toBeInTheDocument(),
  245. );
  246. // Close promotion
  247. await user.click(byRole('button', { name: 'overview.sonarlint_ad.close_promotion' }).get());
  248. expect(
  249. byRole('heading', { name: 'overview.sonarlint_ad.header' }).query(),
  250. ).not.toBeInTheDocument();
  251. });
  252. it('should render correctly 0 New issues onboarding', async () => {
  253. jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
  254. status: 'ERROR',
  255. conditions: [
  256. mockQualityGateProjectCondition({
  257. status: 'ERROR',
  258. errorThreshold: '0',
  259. metricKey: MetricKey.new_violations,
  260. actualValue: '1',
  261. }),
  262. ],
  263. caycStatus: CaycStatus.Compliant,
  264. ignoredConditions: false,
  265. });
  266. jest.mocked(fetchQualityGate).mockResolvedValueOnce(mockQualityGate({ isBuiltIn: true }));
  267. renderPullRequestOverview();
  268. expect(
  269. await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(),
  270. ).toBeInTheDocument();
  271. expect(await byRole('alertdialog').find()).toBeInTheDocument();
  272. });
  273. it('should not render 0 New issues onboarding when user dismissed it', async () => {
  274. jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
  275. status: 'ERROR',
  276. conditions: [
  277. mockQualityGateProjectCondition({
  278. status: 'ERROR',
  279. errorThreshold: '0',
  280. metricKey: MetricKey.new_violations,
  281. actualValue: '1',
  282. }),
  283. ],
  284. caycStatus: CaycStatus.Compliant,
  285. ignoredConditions: false,
  286. });
  287. jest.mocked(fetchQualityGate).mockResolvedValueOnce(mockQualityGate({ isBuiltIn: true }));
  288. renderPullRequestOverview(
  289. {},
  290. mockLoggedInUser({
  291. dismissedNotices: { [NoticeType.OVERVIEW_ZERO_NEW_ISSUES_SIMPLIFICATION]: true },
  292. }),
  293. );
  294. await waitFor(async () =>
  295. expect(
  296. await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(),
  297. ).toBeInTheDocument(),
  298. );
  299. expect(await byRole('alertdialog').query()).not.toBeInTheDocument();
  300. });
  301. function renderPullRequestOverview(
  302. props: Partial<ComponentPropsType<typeof PullRequestOverview>> = {},
  303. currentUser = mockLoggedInUser(),
  304. ) {
  305. renderComponent(
  306. <CurrentUserContextProvider currentUser={currentUser}>
  307. <PullRequestOverview
  308. pullRequest={mockPullRequest()}
  309. component={mockComponent({
  310. breadcrumbs: [mockComponent({ key: 'foo' })],
  311. key: 'foo',
  312. name: 'Foo',
  313. })}
  314. {...props}
  315. />
  316. </CurrentUserContextProvider>,
  317. );
  318. }