index.spec.tsx 10 KB

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