index.spec.tsx 9.7 KB

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