index.spec.tsx 10 KB

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