index.spec.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import {browserHistory} from 'react-router';
  2. import {initializeData} from 'sentry-test/performance/initializePerformanceData';
  3. import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  4. import TeamStore from 'sentry/stores/teamStore';
  5. import {MetricsCardinalityProvider} from 'sentry/utils/performance/contexts/metricsCardinality';
  6. import {OrganizationContext} from 'sentry/views/organizationContext';
  7. import {generatePerformanceEventView} from 'sentry/views/performance/data';
  8. import {PerformanceLanding} from 'sentry/views/performance/landing';
  9. import {REACT_NATIVE_COLUMN_TITLES} from 'sentry/views/performance/landing/data';
  10. import {LandingDisplayField} from 'sentry/views/performance/landing/utils';
  11. import {addMetricsDataMock} from './metricsDataSwitcher.spec';
  12. const WrappedComponent = ({data, withStaticFilters = false}) => {
  13. const eventView = generatePerformanceEventView(data.router.location, data.projects, {
  14. withStaticFilters,
  15. });
  16. return (
  17. <OrganizationContext.Provider value={data.organization}>
  18. <MetricsCardinalityProvider
  19. location={data.router.location}
  20. organization={data.organization}
  21. >
  22. <PerformanceLanding
  23. router={data.router}
  24. organization={data.organization}
  25. location={data.router.location}
  26. eventView={eventView}
  27. projects={data.projects}
  28. selection={eventView.getPageFilters()}
  29. onboardingProject={undefined}
  30. handleSearch={() => {}}
  31. handleTrendsClick={() => {}}
  32. setError={() => {}}
  33. withStaticFilters={withStaticFilters}
  34. />
  35. </MetricsCardinalityProvider>
  36. </OrganizationContext.Provider>
  37. );
  38. };
  39. describe('Performance > Landing > Index', function () {
  40. let eventStatsMock: any;
  41. let eventsV2Mock: any;
  42. let wrapper: any;
  43. act(() => void TeamStore.loadInitialData([], false, null));
  44. beforeEach(function () {
  45. // @ts-ignore no-console
  46. // eslint-disable-next-line no-console
  47. jest.spyOn(console, 'error').mockImplementation(jest.fn());
  48. MockApiClient.addMockResponse({
  49. url: '/organizations/org-slug/sdk-updates/',
  50. body: [],
  51. });
  52. MockApiClient.addMockResponse({
  53. url: '/prompts-activity/',
  54. body: {},
  55. });
  56. MockApiClient.addMockResponse({
  57. url: '/organizations/org-slug/projects/',
  58. body: [],
  59. });
  60. MockApiClient.addMockResponse({
  61. method: 'GET',
  62. url: `/organizations/org-slug/key-transactions-list/`,
  63. body: [],
  64. });
  65. MockApiClient.addMockResponse({
  66. method: 'GET',
  67. url: `/organizations/org-slug/legacy-key-transactions-count/`,
  68. body: [],
  69. });
  70. eventStatsMock = MockApiClient.addMockResponse({
  71. method: 'GET',
  72. url: `/organizations/org-slug/events-stats/`,
  73. body: [],
  74. });
  75. MockApiClient.addMockResponse({
  76. method: 'GET',
  77. url: `/organizations/org-slug/events-trends-stats/`,
  78. body: [],
  79. });
  80. MockApiClient.addMockResponse({
  81. method: 'GET',
  82. url: `/organizations/org-slug/events-histogram/`,
  83. body: [],
  84. });
  85. eventsV2Mock = MockApiClient.addMockResponse({
  86. method: 'GET',
  87. url: `/organizations/org-slug/eventsv2/`,
  88. body: {
  89. meta: {
  90. id: 'string',
  91. },
  92. data: [
  93. {
  94. id: '1234',
  95. },
  96. ],
  97. },
  98. });
  99. MockApiClient.addMockResponse({
  100. method: 'GET',
  101. url: `/organizations/org-slug/events/`,
  102. body: {
  103. data: [{}],
  104. meta: {},
  105. },
  106. });
  107. });
  108. afterEach(function () {
  109. MockApiClient.clearMockResponses();
  110. // @ts-ignore no-console
  111. // eslint-disable-next-line no-console
  112. console.error.mockRestore();
  113. if (wrapper) {
  114. wrapper.unmount();
  115. wrapper = undefined;
  116. }
  117. });
  118. it('renders basic UI elements', function () {
  119. const data = initializeData();
  120. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  121. expect(screen.getByTestId('performance-landing-v3')).toBeInTheDocument();
  122. });
  123. it('renders frontend pageload view', function () {
  124. const data = initializeData({
  125. query: {landingDisplay: LandingDisplayField.FRONTEND_PAGELOAD},
  126. });
  127. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  128. expect(screen.getByTestId('frontend-pageload-view')).toBeInTheDocument();
  129. expect(screen.getByTestId('performance-table')).toBeInTheDocument();
  130. const titles = screen.getAllByTestId('performance-widget-title');
  131. expect(titles).toHaveLength(5);
  132. expect(titles[0]).toHaveTextContent('p75 LCP');
  133. expect(titles[1]).toHaveTextContent('LCP Distribution');
  134. expect(titles[2]).toHaveTextContent('FCP Distribution');
  135. expect(titles[3]).toHaveTextContent('Worst LCP Web Vitals');
  136. expect(titles[4]).toHaveTextContent('Worst FCP Web Vitals');
  137. });
  138. it('renders frontend other view', function () {
  139. const data = initializeData({
  140. query: {landingDisplay: LandingDisplayField.FRONTEND_OTHER},
  141. });
  142. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  143. expect(screen.getByTestId('performance-table')).toBeInTheDocument();
  144. });
  145. it('renders backend view', function () {
  146. const data = initializeData({
  147. query: {landingDisplay: LandingDisplayField.BACKEND},
  148. });
  149. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  150. expect(screen.getByTestId('performance-table')).toBeInTheDocument();
  151. });
  152. it('renders mobile view', function () {
  153. const data = initializeData({
  154. query: {landingDisplay: LandingDisplayField.MOBILE},
  155. });
  156. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  157. expect(screen.getByTestId('performance-table')).toBeInTheDocument();
  158. });
  159. it('renders react-native table headers in mobile view', async function () {
  160. const project = TestStubs.Project({platform: 'react-native'});
  161. const projects = [project];
  162. const data = initializeData({
  163. query: {landingDisplay: LandingDisplayField.MOBILE},
  164. selectedProject: project.id,
  165. projects,
  166. });
  167. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  168. expect(await screen.findByTestId('performance-table')).toBeInTheDocument();
  169. expect(screen.getByTestId('grid-editable')).toBeInTheDocument();
  170. const columnHeaders = await screen.findAllByTestId('grid-head-cell');
  171. expect(columnHeaders).toHaveLength(REACT_NATIVE_COLUMN_TITLES.length);
  172. for (const [index, title] of columnHeaders.entries()) {
  173. expect(title).toHaveTextContent(REACT_NATIVE_COLUMN_TITLES[index]);
  174. }
  175. });
  176. it('renders all transactions view', async function () {
  177. const data = initializeData({
  178. query: {landingDisplay: LandingDisplayField.ALL},
  179. });
  180. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  181. expect(await screen.findByTestId('performance-table')).toBeInTheDocument();
  182. expect(eventStatsMock).toHaveBeenCalledTimes(1); // Only one request is made since the query batcher is working.
  183. expect(eventStatsMock).toHaveBeenNthCalledWith(
  184. 1,
  185. expect.anything(),
  186. expect.objectContaining({
  187. query: expect.objectContaining({
  188. environment: [],
  189. interval: '15m',
  190. partial: '1',
  191. project: [],
  192. query: 'transaction.duration:<15m event.type:transaction',
  193. referrer: 'api.performance.generic-widget-chart.user-misery-area',
  194. statsPeriod: '48h',
  195. yAxis: ['user_misery()', 'tpm()', 'failure_rate()'],
  196. }),
  197. })
  198. );
  199. expect(eventsV2Mock).toHaveBeenCalledTimes(2);
  200. const titles = await screen.findAllByTestId('performance-widget-title');
  201. expect(titles).toHaveLength(5);
  202. expect(titles.at(0)).toHaveTextContent('User Misery');
  203. expect(titles.at(1)).toHaveTextContent('Transactions Per Minute');
  204. expect(titles.at(2)).toHaveTextContent('Failure Rate');
  205. expect(titles.at(3)).toHaveTextContent('Most Related Issues');
  206. expect(titles.at(4)).toHaveTextContent('Most Improved');
  207. });
  208. it('Can switch between landing displays', function () {
  209. const data = initializeData({
  210. query: {landingDisplay: LandingDisplayField.FRONTEND_PAGELOAD, abc: '123'},
  211. });
  212. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  213. expect(screen.getByTestId('frontend-pageload-view')).toBeInTheDocument();
  214. userEvent.click(screen.getByTestId('landing-tab-all'));
  215. expect(browserHistory.push).toHaveBeenNthCalledWith(
  216. 1,
  217. expect.objectContaining({
  218. pathname: data.location.pathname,
  219. query: {query: '', abc: '123'},
  220. })
  221. );
  222. });
  223. it('Updating projects switches performance view', function () {
  224. const data = initializeData({
  225. query: {landingDisplay: LandingDisplayField.FRONTEND_PAGELOAD},
  226. });
  227. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  228. expect(screen.getByTestId('frontend-pageload-view')).toBeInTheDocument();
  229. const updatedData = initializeData({
  230. projects: [TestStubs.Project({id: 123, platform: 'unknown'})],
  231. selectedProject: 123,
  232. });
  233. wrapper.rerender(<WrappedComponent data={updatedData} />, data.routerContext);
  234. expect(screen.getByTestId('all-transactions-view')).toBeInTheDocument();
  235. });
  236. it('View correctly defaults based on project without url param', function () {
  237. const data = initializeData({
  238. projects: [TestStubs.Project({id: 99, platform: 'javascript-react'})],
  239. selectedProject: 99,
  240. });
  241. wrapper = render(<WrappedComponent data={data} />, data.routerContext);
  242. expect(screen.getByTestId('frontend-pageload-view')).toBeInTheDocument();
  243. });
  244. describe('with transaction search feature', function () {
  245. it('renders the search bar', async function () {
  246. addMetricsDataMock();
  247. const data = initializeData({
  248. features: ['performance-transaction-name-only-search'],
  249. query: {
  250. field: 'test',
  251. },
  252. });
  253. wrapper = render(
  254. <WrappedComponent data={data} withStaticFilters />,
  255. data.routerContext
  256. );
  257. expect(await screen.findByTestId('transaction-search-bar')).toBeInTheDocument();
  258. });
  259. });
  260. });