index.spec.tsx 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import {browserHistory} from 'react-router';
  2. import {mountWithTheme} from 'sentry-test/enzyme';
  3. import {initializeData} from 'sentry-test/performance/initializePerformanceData';
  4. import {act} from 'sentry-test/reactTestingLibrary';
  5. import TeamStore from 'sentry/stores/teamStore';
  6. import EventView from 'sentry/utils/discover/eventView';
  7. import {MetricsCardinalityProvider} from 'sentry/utils/performance/contexts/metricsCardinality';
  8. import {OrganizationContext} from 'sentry/views/organizationContext';
  9. import {PerformanceLanding} from 'sentry/views/performance/landing';
  10. import {REACT_NATIVE_COLUMN_TITLES} from 'sentry/views/performance/landing/data';
  11. import * as utils from 'sentry/views/performance/landing/utils';
  12. import {LandingDisplayField} from 'sentry/views/performance/landing/utils';
  13. import {addMetricsDataMock} from './metricsDataSwitcher.spec';
  14. const WrappedComponent = ({data, withStaticFilters = false}) => {
  15. const eventView = EventView.fromLocation(data.router.location);
  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. method: 'GET',
  58. url: `/organizations/org-slug/key-transactions-list/`,
  59. body: [],
  60. });
  61. MockApiClient.addMockResponse({
  62. method: 'GET',
  63. url: `/organizations/org-slug/legacy-key-transactions-count/`,
  64. body: [],
  65. });
  66. eventStatsMock = MockApiClient.addMockResponse({
  67. method: 'GET',
  68. url: `/organizations/org-slug/events-stats/`,
  69. body: [],
  70. });
  71. MockApiClient.addMockResponse({
  72. method: 'GET',
  73. url: `/organizations/org-slug/events-trends-stats/`,
  74. body: [],
  75. });
  76. eventsV2Mock = MockApiClient.addMockResponse({
  77. method: 'GET',
  78. url: `/organizations/org-slug/eventsv2/`,
  79. body: [],
  80. });
  81. MockApiClient.addMockResponse({
  82. method: 'GET',
  83. url: `/organizations/org-slug/events/`,
  84. body: {
  85. data: [{}],
  86. meta: {},
  87. },
  88. });
  89. });
  90. afterEach(function () {
  91. MockApiClient.clearMockResponses();
  92. // @ts-ignore no-console
  93. // eslint-disable-next-line no-console
  94. console.error.mockRestore();
  95. if (wrapper) {
  96. wrapper.unmount();
  97. wrapper = undefined;
  98. }
  99. });
  100. it('renders basic UI elements', async function () {
  101. const data = initializeData();
  102. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  103. await tick();
  104. wrapper.update();
  105. expect(wrapper.find('div[data-test-id="performance-landing-v3"]').exists()).toBe(
  106. true
  107. );
  108. });
  109. it('renders frontend pageload view', async function () {
  110. const data = initializeData({
  111. query: {landingDisplay: LandingDisplayField.FRONTEND_PAGELOAD},
  112. });
  113. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  114. await tick();
  115. wrapper.update();
  116. expect(wrapper.find('div[data-test-id="frontend-pageload-view"]').exists()).toBe(
  117. true
  118. );
  119. expect(wrapper.find('Table')).toHaveLength(1);
  120. const titles = wrapper.find('div[data-test-id="performance-widget-title"]');
  121. expect(titles).toHaveLength(5);
  122. expect(titles.at(0).text()).toEqual('p75 LCP');
  123. expect(titles.at(1).text()).toEqual('LCP Distribution');
  124. expect(titles.at(2).text()).toEqual('FCP Distribution');
  125. expect(titles.at(3).text()).toEqual('Worst LCP Web Vitals');
  126. expect(titles.at(4).text()).toEqual('Worst FCP Web Vitals');
  127. });
  128. it('renders frontend other view', async function () {
  129. const data = initializeData({
  130. query: {landingDisplay: LandingDisplayField.FRONTEND_OTHER},
  131. });
  132. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  133. await tick();
  134. wrapper.update();
  135. expect(wrapper.find('Table').exists()).toBe(true);
  136. });
  137. it('renders backend view', async function () {
  138. const data = initializeData({
  139. query: {landingDisplay: LandingDisplayField.BACKEND},
  140. });
  141. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  142. await tick();
  143. wrapper.update();
  144. expect(wrapper.find('Table').exists()).toBe(true);
  145. });
  146. it('renders mobile view', async function () {
  147. const data = initializeData({
  148. query: {landingDisplay: LandingDisplayField.MOBILE},
  149. });
  150. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  151. await tick();
  152. wrapper.update();
  153. expect(wrapper.find('Table').exists()).toBe(true);
  154. });
  155. it('renders react-native table headers in mobile view', async function () {
  156. jest.spyOn(utils, 'checkIsReactNative').mockReturnValueOnce(true);
  157. const data = initializeData({
  158. query: {landingDisplay: LandingDisplayField.MOBILE},
  159. });
  160. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  161. await tick();
  162. wrapper.update();
  163. const table = wrapper.find('Table');
  164. expect(table.exists()).toBe(true);
  165. expect(table.props().columnTitles).toEqual(REACT_NATIVE_COLUMN_TITLES);
  166. });
  167. it('renders all transactions view', async function () {
  168. const data = initializeData({
  169. query: {landingDisplay: LandingDisplayField.ALL},
  170. });
  171. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  172. await tick();
  173. wrapper.update();
  174. expect(wrapper.find('Table').exists()).toBe(true);
  175. expect(eventStatsMock).toHaveBeenCalledTimes(1); // Only one request is made since the query batcher is working.
  176. expect(eventStatsMock).toHaveBeenNthCalledWith(
  177. 1,
  178. expect.anything(),
  179. expect.objectContaining({
  180. query: expect.objectContaining({
  181. environment: [],
  182. interval: '1h',
  183. partial: '1',
  184. project: [],
  185. query: '',
  186. referrer: 'api.performance.generic-widget-chart.user-misery-area',
  187. statsPeriod: '28d',
  188. yAxis: ['user_misery()', 'tpm()', 'failure_rate()'],
  189. }),
  190. })
  191. );
  192. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  193. const titles = wrapper.find('div[data-test-id="performance-widget-title"]');
  194. expect(titles).toHaveLength(5);
  195. expect(titles.at(0).text()).toEqual('User Misery');
  196. expect(titles.at(1).text()).toEqual('Transactions Per Minute');
  197. expect(titles.at(2).text()).toEqual('Failure Rate');
  198. expect(titles.at(3).text()).toEqual('Most Related Issues');
  199. expect(titles.at(4).text()).toEqual('Most Improved');
  200. });
  201. it('Can switch between landing displays', async function () {
  202. const data = initializeData({
  203. query: {landingDisplay: LandingDisplayField.FRONTEND_PAGELOAD, abc: '123'},
  204. });
  205. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  206. await tick();
  207. wrapper.update();
  208. expect(wrapper.find('div[data-test-id="frontend-pageload-view"]').exists()).toBe(
  209. true
  210. );
  211. wrapper.find('a[data-test-id="landing-tab-all"]').simulate('click');
  212. await tick();
  213. wrapper.update();
  214. expect(browserHistory.push).toHaveBeenNthCalledWith(
  215. 1,
  216. expect.objectContaining({
  217. pathname: data.location.pathname,
  218. query: {query: '', abc: '123'},
  219. })
  220. );
  221. });
  222. it('Updating projects switches performance view', async function () {
  223. const data = initializeData({
  224. query: {landingDisplay: LandingDisplayField.FRONTEND_PAGELOAD},
  225. });
  226. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  227. await tick();
  228. wrapper.update();
  229. expect(wrapper.find('div[data-test-id="frontend-pageload-view"]').exists()).toBe(
  230. true
  231. );
  232. const updatedData = initializeData({
  233. projects: [TestStubs.Project({id: 123, platform: 'unknown'})],
  234. project: 123 as any,
  235. });
  236. wrapper.setProps({
  237. data: updatedData,
  238. } as any);
  239. await tick();
  240. wrapper.update();
  241. expect(wrapper.find('div[data-test-id="all-transactions-view"]').exists()).toBe(true);
  242. });
  243. it('View correctly defaults based on project without url param', async function () {
  244. const data = initializeData({
  245. projects: [TestStubs.Project({id: 99, platform: 'javascript-react'})],
  246. project: 99 as any,
  247. });
  248. wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
  249. await tick();
  250. wrapper.update();
  251. expect(wrapper.find('div[data-test-id="frontend-pageload-view"]').exists()).toBe(
  252. true
  253. );
  254. });
  255. describe('with transaction search feature', function () {
  256. it('renders the search bar', async function () {
  257. addMetricsDataMock();
  258. const data = initializeData({
  259. features: ['performance-transaction-name-only-search'],
  260. query: {
  261. field: 'test',
  262. },
  263. });
  264. wrapper = mountWithTheme(
  265. <WrappedComponent data={data} withStaticFilters />,
  266. data.routerContext
  267. );
  268. await tick();
  269. wrapper.update();
  270. expect(wrapper.find('div[data-test-id="transaction-search-bar"]').exists()).toBe(
  271. true
  272. );
  273. });
  274. });
  275. });