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.

ComponentMeasures-it.tsx 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  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 { times } from 'lodash';
  23. import selectEvent from 'react-select-event';
  24. import BranchesServiceMock from '../../../api/mocks/BranchesServiceMock';
  25. import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock';
  26. import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
  27. import { MeasuresServiceMock } from '../../../api/mocks/MeasuresServiceMock';
  28. import { mockComponent } from '../../../helpers/mocks/component';
  29. import { mockMeasure, mockMetric } from '../../../helpers/testMocks';
  30. import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils';
  31. import { byLabelText, byRole, byTestId, byText } from '../../../helpers/testSelector';
  32. import { ComponentContextShape, ComponentQualifier } from '../../../types/component';
  33. import { Feature } from '../../../types/features';
  34. import { MetricKey } from '../../../types/metrics';
  35. import routes from '../routes';
  36. jest.mock('lodash', () => ({
  37. ...jest.requireActual('lodash'),
  38. throttle: (fn: (...args: unknown[]) => unknown) => fn,
  39. }));
  40. jest.mock('../../../api/metrics', () => {
  41. const { DEFAULT_METRICS } = jest.requireActual('../../../helpers/mocks/metrics');
  42. const metrics = Object.values(MetricKey).map(
  43. (key) => DEFAULT_METRICS[key] ?? mockMetric({ key }),
  44. );
  45. return {
  46. getAllMetrics: jest.fn().mockResolvedValue(metrics),
  47. };
  48. });
  49. const componentsHandler = new ComponentsServiceMock();
  50. const measuresHandler = new MeasuresServiceMock();
  51. const issuesHandler = new IssuesServiceMock();
  52. const branchHandler = new BranchesServiceMock();
  53. afterEach(() => {
  54. componentsHandler.reset();
  55. measuresHandler.reset();
  56. issuesHandler.reset();
  57. branchHandler.reset();
  58. });
  59. describe('rendering', () => {
  60. it('should correctly render the default overview and navigation', async () => {
  61. const { ui, user } = getPageObject();
  62. renderMeasuresApp();
  63. await ui.appLoaded();
  64. // Overview.
  65. expect(ui.seeDataAsListLink.get()).toBeInTheDocument();
  66. expect(ui.overviewDomainBtn.get()).toHaveAttribute('aria-current', 'true');
  67. expect(ui.bubbleChart.get()).toBeInTheDocument();
  68. expect(within(ui.bubbleChart.get()).getAllByRole('link')).toHaveLength(8);
  69. expect(ui.newCodePeriodTxt.get()).toBeInTheDocument();
  70. // Sidebar.
  71. expect(ui.reliabilityDomainBtn.get()).toBeInTheDocument();
  72. expect(ui.securityDomainBtn.get()).toBeInTheDocument();
  73. expect(ui.securityReviewDomainBtn.get()).toBeInTheDocument();
  74. expect(ui.maintainabilityDomainBtn.get()).toBeInTheDocument();
  75. expect(ui.coverageDomainBtn.get()).toBeInTheDocument();
  76. expect(ui.duplicationsDomainBtn.get()).toBeInTheDocument();
  77. expect(ui.sizeDomainBtn.get()).toBeInTheDocument();
  78. expect(ui.complexityDomainBtn.get()).toBeInTheDocument();
  79. expect(ui.issuesDomainBtn.get()).toBeInTheDocument();
  80. // Check one of the domains.
  81. await user.click(ui.maintainabilityDomainBtn.get());
  82. [
  83. 'component_measures.metric.new_maintainability_issues.name 5',
  84. 'Added Technical Debt work_duration.x_minutes.1',
  85. 'Technical Debt Ratio on New Code 1.0%',
  86. 'Maintainability Rating on New Code metric.has_rating_X.E',
  87. 'component_measures.metric.maintainability_issues.name 2',
  88. 'Technical Debt work_duration.x_minutes.1',
  89. 'Technical Debt Ratio 1.0%',
  90. 'Maintainability Rating metric.has_rating_X.E',
  91. 'Effort to Reach Maintainability Rating A work_duration.x_minutes.1',
  92. ].forEach((measure) => {
  93. expect(ui.measureBtn(measure).get()).toBeInTheDocument();
  94. });
  95. });
  96. it('should correctly revert to old measures when analysis is missing', async () => {
  97. measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
  98. measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues);
  99. const { ui, user } = getPageObject();
  100. renderMeasuresApp();
  101. await ui.appLoaded();
  102. // Check one of the domains.
  103. await user.click(ui.maintainabilityDomainBtn.get());
  104. [
  105. 'component_measures.metric.new_code_smells.name 8',
  106. 'Added Technical Debt work_duration.x_minutes.1',
  107. 'Technical Debt Ratio on New Code 1.0%',
  108. 'Maintainability Rating on New Code metric.has_rating_X.E',
  109. 'component_measures.metric.code_smells.name 8',
  110. 'Technical Debt work_duration.x_minutes.1',
  111. 'Technical Debt Ratio 1.0%',
  112. 'Maintainability Rating metric.has_rating_X.E',
  113. 'Effort to Reach Maintainability Rating A work_duration.x_minutes.1',
  114. ].forEach((measure) => {
  115. expect(ui.measureBtn(measure).get()).toBeInTheDocument();
  116. });
  117. expect(screen.getByText('overview.missing_project_data.TRK')).toBeInTheDocument();
  118. });
  119. it('should correctly render a list view', async () => {
  120. const { ui } = getPageObject();
  121. renderMeasuresApp('component_measures?id=foo&metric=code_smells&view=list');
  122. await ui.appLoaded();
  123. expect(ui.measuresTable.get()).toBeInTheDocument();
  124. expect(ui.measuresRows.getAll()).toHaveLength(8);
  125. });
  126. it('should correctly render a tree view', async () => {
  127. const { ui } = getPageObject();
  128. renderMeasuresApp('component_measures?id=foo&metric=code_smells&view=tree');
  129. await ui.appLoaded();
  130. expect(ui.measuresTable.get()).toBeInTheDocument();
  131. expect(ui.measuresRows.getAll()).toHaveLength(7);
  132. });
  133. it('should correctly render a rating treemap view', async () => {
  134. const { ui } = getPageObject();
  135. renderMeasuresApp('component_measures?id=foo&metric=sqale_rating&view=treemap');
  136. await ui.appLoaded();
  137. expect(ui.treeMap.byRole('link').getAll()).toHaveLength(7);
  138. expect(ui.treeMapCell(/folderA .+ Maintainability Rating: C/).get()).toBeInTheDocument();
  139. expect(ui.treeMapCell(/test1\.js .+ Maintainability Rating: B/).get()).toBeInTheDocument();
  140. expect(ui.treeMapCell(/index\.tsx .+ Maintainability Rating: A/).get()).toBeInTheDocument();
  141. });
  142. it('should correctly render a percent treemap view', async () => {
  143. const { measures } = componentsHandler;
  144. measures['foo:folderA'][MetricKey.coverage] = {
  145. metric: MetricKey.coverage,
  146. value: '74.2',
  147. };
  148. measures['foo:test1.js'][MetricKey.coverage] = {
  149. metric: MetricKey.coverage,
  150. value: undefined,
  151. };
  152. measures['foo:index.tsx'][MetricKey.coverage] = {
  153. metric: MetricKey.coverage,
  154. value: '13.1',
  155. };
  156. const { ui } = getPageObject();
  157. renderMeasuresApp('component_measures?id=foo&metric=coverage&view=treemap');
  158. await ui.appLoaded();
  159. expect(ui.treeMap.byRole('link').getAll()).toHaveLength(7);
  160. expect(ui.treeMapCell(/folderA .+ Coverage: 74.2%/).get()).toBeInTheDocument();
  161. expect(ui.treeMapCell(/test1\.js .+ Coverage: —/).get()).toBeInTheDocument();
  162. expect(ui.treeMapCell(/index\.tsx .+ Coverage: 13.1%/).get()).toBeInTheDocument();
  163. });
  164. it('should render correctly for an unknown metric', async () => {
  165. const { ui } = getPageObject();
  166. renderMeasuresApp('component_measures?id=foo&metric=unknown');
  167. await ui.appLoaded();
  168. // Fall back to a known metric.
  169. expect(screen.getAllByText('Releasability rating').length).toBeGreaterThan(0);
  170. });
  171. it('should render issues measures when query by open_issues', async () => {
  172. const { ui } = getPageObject();
  173. renderMeasuresApp('component_measures?id=foo&metric=open_issues');
  174. await ui.appLoaded();
  175. expect(screen.getAllByText('Issues').length).toEqual(1);
  176. [
  177. 'component_measures.metric.new_violations.name 1',
  178. 'component_measures.metric.violations.name 1',
  179. 'component_measures.metric.confirmed_issues.name 1',
  180. 'component_measures.metric.accepted_issues.name 1',
  181. 'component_measures.metric.new_accepted_issues.name 1',
  182. 'component_measures.metric.false_positive_issues.name 1',
  183. ].forEach((measure) => {
  184. expect(ui.measureBtn(measure).get()).toBeInTheDocument();
  185. });
  186. });
  187. it('should render correctly if there are no measures', async () => {
  188. componentsHandler.registerComponentMeasures({});
  189. measuresHandler.registerComponentMeasures({});
  190. const { ui } = getPageObject();
  191. renderMeasuresApp();
  192. await ui.appLoaded();
  193. expect(ui.emptyText.get()).toBeInTheDocument();
  194. });
  195. it('should render correctly if on a pull request and viewing coverage', async () => {
  196. const { ui } = getPageObject();
  197. renderMeasuresApp('component_measures?id=foo&metric=coverage&pullRequest=01');
  198. await ui.appLoaded();
  199. expect(await ui.detailsUnavailableText.find()).toBeInTheDocument();
  200. });
  201. it('should render a warning message if the user does not have access to all components', async () => {
  202. const { ui } = getPageObject();
  203. renderMeasuresApp('component_measures?id=foo&metric=code_smells', {
  204. component: mockComponent({
  205. key: 'foo',
  206. qualifier: ComponentQualifier.Portfolio,
  207. canBrowseAllChildProjects: false,
  208. }),
  209. });
  210. await ui.appLoaded();
  211. expect(
  212. within(ui.noAccessWarning.get()).getByText('component_measures.not_all_measures_are_shown'),
  213. ).toBeInTheDocument();
  214. });
  215. it('should correctly render the language distribution', async () => {
  216. const { ui } = getPageObject();
  217. renderMeasuresApp('component_measures?id=foo&metric=ncloc');
  218. await ui.appLoaded();
  219. expect(screen.getByText('10short_number_suffix.k')).toBeInTheDocument();
  220. expect(screen.getByText('java')).toBeInTheDocument();
  221. expect(screen.getByText('5short_number_suffix.k')).toBeInTheDocument();
  222. expect(screen.getByText('javascript')).toBeInTheDocument();
  223. expect(screen.getByText('1short_number_suffix.k')).toBeInTheDocument();
  224. expect(screen.getByText('css')).toBeInTheDocument();
  225. });
  226. it('should only show the best values in list mode', async () => {
  227. const tree = componentsHandler.findComponentTree('foo');
  228. /* eslint-disable-next-line jest/no-conditional-in-test */
  229. if (!tree) {
  230. throw new Error('Could not find base tree');
  231. }
  232. const measures = measuresHandler.getComponentMeasures();
  233. /* eslint-disable-next-line testing-library/no-node-access */
  234. tree.children.push(
  235. ...times(100, (n) => ({
  236. component: mockComponent({
  237. key: `foo:file${n}`,
  238. name: `file${n}`,
  239. qualifier: ComponentQualifier.File,
  240. }),
  241. ancestors: [tree.component],
  242. children: [],
  243. })),
  244. );
  245. componentsHandler.registerComponentTree(tree);
  246. measuresHandler.registerComponentMeasures(
  247. times(100, (n) => `foo:file${n}`).reduce((acc, key) => {
  248. acc[key] = {
  249. [MetricKey.sqale_rating]: mockMeasure({
  250. metric: MetricKey.sqale_rating,
  251. value: '1.0',
  252. bestValue: true,
  253. }),
  254. };
  255. return acc;
  256. }, measures),
  257. );
  258. const { ui, user } = getPageObject();
  259. renderMeasuresApp('component_measures?id=foo&metric=sqale_rating&view=list');
  260. await ui.appLoaded();
  261. expect(ui.notShowingAllComponentsTxt.get()).toBeInTheDocument();
  262. await user.click(ui.showAllBtn.get());
  263. expect(ui.notShowingAllComponentsTxt.query()).not.toBeInTheDocument();
  264. });
  265. it('should correctly render a link to the activity page', async () => {
  266. const { ui, user } = getPageObject();
  267. renderMeasuresApp('component_measures?id=foo&metric=new_maintainability_issues');
  268. await ui.appLoaded();
  269. expect(ui.goToActivityLink.query()).not.toBeInTheDocument();
  270. await user.click(
  271. ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
  272. );
  273. expect(ui.goToActivityLink.get()).toHaveAttribute(
  274. 'href',
  275. '/project/activity?id=foo&graph=custom&custom_metrics=maintainability_issues',
  276. );
  277. });
  278. it('should not render View select options for application metrics', async () => {
  279. const { ui } = getPageObject();
  280. const app = mockComponent({ key: 'app', qualifier: ComponentQualifier.Application });
  281. const tree = {
  282. component: app,
  283. children: [],
  284. ancestors: [],
  285. };
  286. componentsHandler.registerComponentTree(tree, true);
  287. measuresHandler.setComponents(tree);
  288. renderMeasuresApp('component_measures?id=app&metric=new_code_smells', { component: app });
  289. await ui.appLoaded();
  290. expect(ui.viewSelect.query()).not.toBeInTheDocument();
  291. });
  292. });
  293. describe('navigation', () => {
  294. it('should be able to drilldown through the file structure', async () => {
  295. const { ui, user } = getPageObject();
  296. renderMeasuresApp();
  297. await ui.appLoaded();
  298. // Drilldown to the file level.
  299. await user.click(ui.maintainabilityDomainBtn.get());
  300. await user.click(
  301. ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
  302. );
  303. expect(
  304. within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '2' }),
  305. ).toBeInTheDocument();
  306. expect(
  307. within(ui.measuresRow('test1.js').get()).getByRole('cell', { name: '2' }),
  308. ).toBeInTheDocument();
  309. await user.click(ui.fileLink('folderA').get());
  310. expect(
  311. within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '2' }),
  312. ).toBeInTheDocument();
  313. expect(
  314. within(ui.measuresRow('in.tsx').get()).getByRole('cell', { name: '2' }),
  315. ).toBeInTheDocument();
  316. await user.click(ui.fileLink('out.tsx').get());
  317. expect((await ui.sourceCode.findAll()).length).toBeGreaterThan(0);
  318. // Go back using the breadcrumbs.
  319. await user.click(ui.breadcrumbLink('folderA').get());
  320. expect(ui.measuresRow('out.tsx').get()).toBeInTheDocument();
  321. expect(ui.measuresRow('in.tsx').get()).toBeInTheDocument();
  322. });
  323. it('should be able to drilldown thanks to a files list', async () => {
  324. const { ui, user } = getPageObject();
  325. renderMeasuresApp();
  326. await ui.appLoaded();
  327. await user.click(ui.maintainabilityDomainBtn.get());
  328. await user.click(
  329. ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
  330. );
  331. await waitFor(() => ui.changeViewToList());
  332. expect(
  333. within(await ui.measuresRow('out.tsx').find()).getByRole('cell', { name: '2' }),
  334. ).toBeInTheDocument();
  335. expect(
  336. within(ui.measuresRow('test1.js').get()).getByRole('cell', { name: '2' }),
  337. ).toBeInTheDocument();
  338. await user.click(ui.fileLink('out.tsx').get());
  339. expect((await ui.sourceCode.findAll()).length).toBeGreaterThan(0);
  340. });
  341. it('should be able to drilldown thanks to a tree map', async () => {
  342. const { ui, user } = getPageObject();
  343. renderMeasuresApp();
  344. await ui.appLoaded();
  345. await user.click(ui.maintainabilityDomainBtn.get());
  346. await user.click(ui.measureBtn('Maintainability Rating metric.has_rating_X.E').get());
  347. await waitFor(() => ui.changeViewToTreeMap());
  348. expect(await ui.treeMapCell(/folderA/).find()).toBeInTheDocument();
  349. expect(ui.treeMapCell(/test1\.js/).get()).toBeInTheDocument();
  350. await user.click(ui.treeMapCell(/folderA/).get());
  351. expect(ui.treeMapCell(/out\.tsx/).get()).toBeInTheDocument();
  352. expect(ui.treeMapCell(/in\.tsx/).get()).toBeInTheDocument();
  353. await user.click(ui.treeMapCell(/out.tsx/).get());
  354. expect((await ui.sourceCode.findAll()).length).toBeGreaterThan(0);
  355. });
  356. it('should be able to drilldown using the keyboard', async () => {
  357. const { ui, user } = getPageObject();
  358. renderMeasuresApp();
  359. await ui.appLoaded();
  360. // Drilldown to the file level.
  361. await user.click(ui.maintainabilityDomainBtn.get());
  362. await user.click(
  363. ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(),
  364. );
  365. await ui.arrowDown(); // Select the 1st element ("folderA")
  366. await ui.arrowRight(); // Open "folderA"
  367. expect(
  368. within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '2' }),
  369. ).toBeInTheDocument();
  370. expect(
  371. within(ui.measuresRow('in.tsx').get()).getByRole('cell', { name: '2' }),
  372. ).toBeInTheDocument();
  373. // Move back to project
  374. await ui.arrowLeft(); // Close "folderA"
  375. expect(
  376. within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '2' }),
  377. ).toBeInTheDocument();
  378. await ui.arrowRight(); // Open "folderA"
  379. await ui.arrowDown(); // Select the 1st element ("out.tsx")
  380. await ui.arrowRight(); // Open "out.tsx"
  381. expect((await ui.sourceCode.findAll()).length).toBeGreaterThan(0);
  382. expect(screen.getAllByText('out.tsx').length).toBeGreaterThan(0);
  383. });
  384. });
  385. describe('redirects', () => {
  386. it('should redirect old history route', () => {
  387. renderMeasuresApp('component_measures/metric/bugs/history?id=foo');
  388. expect(
  389. screen.getByText('/project/activity?id=foo&graph=custom&custom_metrics=bugs'),
  390. ).toBeInTheDocument();
  391. });
  392. it('should redirect old metric route', async () => {
  393. measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
  394. measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues);
  395. const { ui } = getPageObject();
  396. renderMeasuresApp('component_measures/metric/bugs?id=foo');
  397. await ui.appLoaded();
  398. expect(ui.measureBtn('component_measures.metric.bugs.name 0').get()).toHaveAttribute(
  399. 'aria-current',
  400. 'true',
  401. );
  402. });
  403. it('should redirect old metric route for software qualities', async () => {
  404. const { ui } = getPageObject();
  405. renderMeasuresApp('component_measures/metric/security_issues?id=foo');
  406. await ui.appLoaded();
  407. expect(ui.measureBtn('component_measures.metric.security_issues.name 1').get()).toHaveAttribute(
  408. 'aria-current',
  409. 'true',
  410. );
  411. });
  412. it('should redirect old domain route', async () => {
  413. measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues);
  414. measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues);
  415. const { ui } = getPageObject();
  416. renderMeasuresApp('component_measures/domain/bugs?id=foo');
  417. await ui.appLoaded();
  418. expect(ui.reliabilityDomainBtn.get()).toHaveAttribute('aria-expanded', 'true');
  419. });
  420. it('should redirect old domain route for software qualities', async () => {
  421. const { ui } = getPageObject();
  422. renderMeasuresApp('component_measures/domain/reliability_issues?id=foo');
  423. await ui.appLoaded();
  424. expect(ui.reliabilityDomainBtn.get()).toHaveAttribute('aria-expanded', 'true');
  425. });
  426. });
  427. it('should allow to load more components', async () => {
  428. const tree = componentsHandler.findComponentTree('foo');
  429. /* eslint-disable-next-line jest/no-conditional-in-test */
  430. if (!tree) {
  431. throw new Error('Could not find base tree');
  432. }
  433. /* eslint-disable-next-line testing-library/no-node-access */
  434. tree.children.push(
  435. ...times(1000, (n) => ({
  436. component: mockComponent({
  437. key: `foo:file${n}`,
  438. name: `file${n}`,
  439. qualifier: ComponentQualifier.File,
  440. }),
  441. ancestors: [tree.component],
  442. children: [],
  443. })),
  444. );
  445. componentsHandler.registerComponentTree(tree);
  446. const { ui, user } = getPageObject();
  447. renderMeasuresApp('component_measures?id=foo&metric=code_smells&view=list');
  448. await ui.appLoaded();
  449. await user.click(ui.showAllBtn.get());
  450. expect(ui.showingOutOfTxt('500', '1,008').get()).toBeInTheDocument();
  451. await ui.clickLoadMore();
  452. expect(ui.showingOutOfTxt('1,000', '1,008').get()).toBeInTheDocument();
  453. });
  454. function getPageObject() {
  455. const user = userEvent.setup();
  456. const selectors = {
  457. // Overview
  458. seeDataAsListLink: byRole('link', { name: 'component_measures.overview.see_data_as_list' }),
  459. bubbleChart: byTestId('bubble-chart'),
  460. newCodePeriodTxt: byText('component_measures.leak_legend.new_code'),
  461. // Navigation
  462. overviewDomainBtn: byRole('button', {
  463. name: 'component_measures.overview.project_overview.subnavigation',
  464. }),
  465. releasabilityDomainBtn: byRole('button', {
  466. name: 'Releasability component_measures.domain_subnavigation.Releasability.help',
  467. }),
  468. reliabilityDomainBtn: byRole('button', {
  469. name: 'Reliability component_measures.domain_subnavigation.Reliability.help',
  470. }),
  471. securityDomainBtn: byRole('button', {
  472. name: 'Security component_measures.domain_subnavigation.Security.help',
  473. }),
  474. securityReviewDomainBtn: byRole('button', {
  475. name: 'SecurityReview component_measures.domain_subnavigation.SecurityReview.help',
  476. }),
  477. maintainabilityDomainBtn: byRole('button', {
  478. name: 'Maintainability component_measures.domain_subnavigation.Maintainability.help',
  479. }),
  480. coverageDomainBtn: byRole('button', {
  481. name: 'Coverage component_measures.domain_subnavigation.Coverage.help',
  482. }),
  483. duplicationsDomainBtn: byRole('button', {
  484. name: 'Duplications component_measures.domain_subnavigation.Duplications.help',
  485. }),
  486. sizeDomainBtn: byRole('button', {
  487. name: 'Size component_measures.domain_subnavigation.Size.help',
  488. }),
  489. complexityDomainBtn: byRole('button', {
  490. name: 'Complexity component_measures.domain_subnavigation.Complexity.help',
  491. }),
  492. issuesDomainBtn: byRole('button', {
  493. name: 'Issues component_measures.domain_subnavigation.Issues.help',
  494. }),
  495. measureBtn: (name: string) => byRole('button', { name }),
  496. // Measure content
  497. measuresTable: byRole('table'),
  498. measuresRows: byRole('row'),
  499. measuresRow: (name: string) => byRole('row', { name: new RegExp(name) }),
  500. treeMap: byTestId('treemap'),
  501. treeMapCells: byRole('link'),
  502. treeMapCell: (name: string | RegExp) => byRole('link', { name }),
  503. fileLink: (name: string) => byRole('link', { name }),
  504. sourceCode: byText('function Test() {}'),
  505. notShowingAllComponentsTxt: byText(/component_measures.hidden_best_score_metrics/),
  506. // Misc
  507. loading: byText('loading'),
  508. breadcrumbLink: (name: string) => byRole('link', { name }),
  509. viewSelect: byLabelText('component_measures.view_as'),
  510. emptyText: byText('component_measures.empty'),
  511. detailsUnavailableText: byText('component_measures.details_are_not_available'),
  512. noAccessWarning: byText('component_measures.not_all_measures_are_shown'),
  513. showingOutOfTxt: (x: string, y: string) => byText(`x_of_y_shown.${x}.${y}`),
  514. showAllBtn: byRole('button', {
  515. name: 'component_measures.hidden_best_score_metrics_show_label',
  516. }),
  517. goToActivityLink: byRole('link', { name: 'component_measures.see_metric_history' }),
  518. };
  519. const ui = {
  520. ...selectors,
  521. async appLoaded() {
  522. await waitFor(() => {
  523. expect(selectors.loading.query()).not.toBeInTheDocument();
  524. });
  525. },
  526. async changeViewToList() {
  527. await selectEvent.select(ui.viewSelect.get(), 'component_measures.tab.list');
  528. },
  529. async changeViewToTreeMap() {
  530. await selectEvent.select(ui.viewSelect.get(), 'component_measures.tab.treemap');
  531. },
  532. async arrowDown() {
  533. await user.keyboard('[ArrowDown]');
  534. },
  535. async arrowRight() {
  536. await user.keyboard('[ArrowRight]');
  537. },
  538. async arrowLeft() {
  539. await user.keyboard('[ArrowLeft]');
  540. },
  541. async clickLoadMore() {
  542. await user.click(screen.getByRole('button', { name: 'show_more' }));
  543. },
  544. };
  545. return {
  546. ui,
  547. user,
  548. };
  549. }
  550. function renderMeasuresApp(navigateTo?: string, componentContext?: Partial<ComponentContextShape>) {
  551. return renderAppWithComponentContext(
  552. 'component_measures?id=foo',
  553. routes,
  554. { navigateTo, featureList: [Feature.BranchSupport] },
  555. { component: mockComponent({ key: 'foo' }), ...componentContext },
  556. );
  557. }