index.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. import type {Location} from 'history';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {ProjectFixture} from 'sentry-fixture/project';
  4. import {render, screen, waitFor, within} from 'sentry-test/reactTestingLibrary';
  5. import {browserHistory} from 'sentry/utils/browserHistory';
  6. import localStorage from 'sentry/utils/localStorage';
  7. import {useLocation} from 'sentry/utils/useLocation';
  8. import usePageFilters from 'sentry/utils/usePageFilters';
  9. import useProjects from 'sentry/utils/useProjects';
  10. import {useOnboardingProject} from 'sentry/views/performance/browser/webVitals/utils/useOnboardingProject';
  11. import ScreenLoadSpans from 'sentry/views/performance/mobile/screenload/screenLoadSpans';
  12. jest.mock('sentry/views/performance/browser/webVitals/utils/useOnboardingProject');
  13. jest.mock('sentry/utils/useLocation');
  14. jest.mock('sentry/utils/usePageFilters');
  15. jest.mock('sentry/utils/useProjects');
  16. function mockResponses(organization, project) {
  17. jest.mocked(useOnboardingProject).mockReturnValue(undefined);
  18. jest.mocked(useProjects).mockReturnValue({
  19. fetchError: null,
  20. fetching: false,
  21. hasMore: false,
  22. initiallyLoaded: false,
  23. onSearch: jest.fn(),
  24. placeholders: [],
  25. projects: [project],
  26. });
  27. jest.mocked(useLocation).mockReturnValue({
  28. action: 'PUSH',
  29. hash: '',
  30. key: '',
  31. pathname: '/organizations/org-slug/performance/mobile/screens/spans/',
  32. query: {
  33. project: project.id,
  34. transaction: 'MainActivity',
  35. primaryRelease: 'com.example.vu.android@2.10.5',
  36. secondaryRelease: 'com.example.vu.android@2.10.3+42',
  37. },
  38. search: '',
  39. state: undefined,
  40. } as Location);
  41. jest.mocked(usePageFilters).mockReturnValue({
  42. isReady: true,
  43. desyncedFilters: new Set(),
  44. pinnedFilters: new Set(),
  45. shouldPersist: true,
  46. selection: {
  47. datetime: {
  48. period: '10d',
  49. start: null,
  50. end: null,
  51. utc: false,
  52. },
  53. environments: [],
  54. projects: [parseInt(project.id, 10)],
  55. },
  56. });
  57. MockApiClient.addMockResponse({
  58. url: `/organizations/${organization.slug}/releases/`,
  59. body: [
  60. {
  61. id: 970136705,
  62. version: 'com.example.vu.android@2.10.5',
  63. dateCreated: '2023-12-19T21:37:53.895495Z',
  64. },
  65. {
  66. id: 969902997,
  67. version: 'com.example.vu.android@2.10.3+42',
  68. dateCreated: '2023-12-19T18:04:06.953025Z',
  69. },
  70. ],
  71. });
  72. MockApiClient.addMockResponse({
  73. url: `/organizations/${organization.slug}/events/`,
  74. query: {
  75. dataset: 'metrics',
  76. environment: [],
  77. field: ['release', 'count()'],
  78. per_page: 50,
  79. project: ['2'],
  80. query:
  81. 'transaction.op:ui.load release:["com.example.vu.android@2.10.5","com.example.vu.android@2.10.3+42"]',
  82. referrer: 'api.starfish.mobile-release-selector',
  83. statsPeriod: '10d',
  84. },
  85. body: {
  86. meta: {},
  87. data: [
  88. {
  89. release: 'com.example.vu.android@2.10.5',
  90. 'count()': 9768,
  91. },
  92. {
  93. release: 'com.example.vu.android@2.10.3+42',
  94. 'count()': 826,
  95. },
  96. ],
  97. },
  98. });
  99. }
  100. describe('Screen Summary', function () {
  101. describe('Cross Platform Project', function () {
  102. let eventsMock;
  103. let eventsStatsMock;
  104. let organization;
  105. beforeEach(function () {
  106. const project = ProjectFixture({platform: 'react-native'});
  107. organization = OrganizationFixture({
  108. features: ['insights-initial-modules'],
  109. projects: [project],
  110. });
  111. mockResponses(organization, project);
  112. localStorage.clear();
  113. browserHistory.push = jest.fn();
  114. eventsMock = MockApiClient.addMockResponse({
  115. url: `/organizations/${organization.slug}/events/`,
  116. });
  117. eventsStatsMock = MockApiClient.addMockResponse({
  118. url: `/organizations/${organization.slug}/events-stats/`,
  119. });
  120. });
  121. afterEach(function () {
  122. MockApiClient.clearMockResponses();
  123. jest.clearAllMocks();
  124. });
  125. it('appends os.name filter for react native projects', async function () {
  126. render(<ScreenLoadSpans />, {organization});
  127. // Event samples
  128. await waitFor(() => {
  129. expect(eventsMock).toHaveBeenCalledWith(
  130. expect.anything(),
  131. expect.objectContaining({
  132. query: expect.objectContaining({
  133. dataset: 'discover',
  134. query:
  135. 'transaction.op:ui.load transaction:MainActivity release:com.example.vu.android@2.10.5 os.name:Android',
  136. }),
  137. })
  138. );
  139. });
  140. await waitFor(() => {
  141. expect(eventsMock).toHaveBeenCalledWith(
  142. expect.anything(),
  143. expect.objectContaining({
  144. query: expect.objectContaining({
  145. dataset: 'discover',
  146. query:
  147. 'transaction.op:ui.load transaction:MainActivity release:com.example.vu.android@2.10.3+42 os.name:Android',
  148. }),
  149. })
  150. );
  151. });
  152. // Span Table
  153. await waitFor(() => {
  154. expect(eventsMock).toHaveBeenCalledWith(
  155. expect.anything(),
  156. expect.objectContaining({
  157. query: expect.objectContaining({
  158. dataset: 'spansMetrics',
  159. query:
  160. 'transaction.op:ui.load transaction:MainActivity has:span.description span.op:[file.read,file.write,ui.load,http.client,db,db.sql.room,db.sql.query,db.sql.transaction] os.name:Android release:[com.example.vu.android@2.10.5,com.example.vu.android@2.10.3+42]',
  161. }),
  162. })
  163. );
  164. });
  165. // Chart
  166. await waitFor(() => {
  167. expect(eventsMock).toHaveBeenCalledWith(
  168. expect.anything(),
  169. expect.objectContaining({
  170. query: expect.objectContaining({
  171. dataset: 'metrics',
  172. query:
  173. 'event.type:transaction transaction.op:ui.load transaction:MainActivity os.name:Android release:[com.example.vu.android@2.10.5,com.example.vu.android@2.10.3+42]',
  174. }),
  175. })
  176. );
  177. });
  178. await waitFor(() => {
  179. expect(eventsStatsMock).toHaveBeenCalledWith(
  180. expect.anything(),
  181. expect.objectContaining({
  182. query: expect.objectContaining({
  183. dataset: 'metrics',
  184. query:
  185. 'event.type:transaction transaction.op:ui.load transaction:MainActivity os.name:Android release:[com.example.vu.android@2.10.5,com.example.vu.android@2.10.3+42]',
  186. }),
  187. })
  188. );
  189. });
  190. });
  191. });
  192. describe('Native Project', function () {
  193. let eventsMock;
  194. let eventsStatsMock;
  195. let organization;
  196. beforeEach(function () {
  197. const project = ProjectFixture({platform: 'android'});
  198. organization = OrganizationFixture({
  199. features: ['insights-initial-modules'],
  200. projects: [project],
  201. });
  202. mockResponses(organization, project);
  203. localStorage.clear();
  204. browserHistory.push = jest.fn();
  205. eventsMock = MockApiClient.addMockResponse({
  206. url: `/organizations/${organization.slug}/events/`,
  207. });
  208. eventsStatsMock = MockApiClient.addMockResponse({
  209. url: `/organizations/${organization.slug}/events-stats/`,
  210. });
  211. });
  212. afterEach(function () {
  213. MockApiClient.clearMockResponses();
  214. jest.clearAllMocks();
  215. });
  216. it('does not append os.name filter for native projects', async function () {
  217. render(<ScreenLoadSpans />, {organization});
  218. // Event samples
  219. await waitFor(() => {
  220. expect(eventsMock).toHaveBeenCalledWith(
  221. expect.anything(),
  222. expect.objectContaining({
  223. query: expect.objectContaining({
  224. dataset: 'discover',
  225. query:
  226. 'transaction.op:ui.load transaction:MainActivity release:com.example.vu.android@2.10.5',
  227. }),
  228. })
  229. );
  230. });
  231. await waitFor(() => {
  232. expect(eventsMock).toHaveBeenCalledWith(
  233. expect.anything(),
  234. expect.objectContaining({
  235. query: expect.objectContaining({
  236. dataset: 'discover',
  237. query:
  238. 'transaction.op:ui.load transaction:MainActivity release:com.example.vu.android@2.10.3+42',
  239. }),
  240. })
  241. );
  242. });
  243. // Span Table
  244. await waitFor(() => {
  245. expect(eventsMock).toHaveBeenCalledWith(
  246. expect.anything(),
  247. expect.objectContaining({
  248. query: expect.objectContaining({
  249. dataset: 'spansMetrics',
  250. query:
  251. 'transaction.op:ui.load transaction:MainActivity has:span.description span.op:[file.read,file.write,ui.load,http.client,db,db.sql.room,db.sql.query,db.sql.transaction] release:[com.example.vu.android@2.10.5,com.example.vu.android@2.10.3+42]',
  252. }),
  253. })
  254. );
  255. });
  256. // Chart
  257. await waitFor(() => {
  258. expect(eventsMock).toHaveBeenCalledWith(
  259. expect.anything(),
  260. expect.objectContaining({
  261. query: expect.objectContaining({
  262. dataset: 'metrics',
  263. query:
  264. 'event.type:transaction transaction.op:ui.load transaction:MainActivity release:[com.example.vu.android@2.10.5,com.example.vu.android@2.10.3+42]',
  265. }),
  266. })
  267. );
  268. });
  269. await waitFor(() => {
  270. expect(eventsStatsMock).toHaveBeenCalledWith(
  271. expect.anything(),
  272. expect.objectContaining({
  273. query: expect.objectContaining({
  274. dataset: 'metrics',
  275. query:
  276. 'event.type:transaction transaction.op:ui.load transaction:MainActivity release:[com.example.vu.android@2.10.5,com.example.vu.android@2.10.3+42]',
  277. }),
  278. })
  279. );
  280. });
  281. });
  282. it('renders the top level metrics data correctly', async function () {
  283. eventsMock = MockApiClient.addMockResponse({
  284. url: `/organizations/${organization.slug}/events/`,
  285. body: {
  286. data: [
  287. {
  288. 'avg_if(measurements.time_to_initial_display,release,com.example.vu.android@2.10.5)': 1000,
  289. 'avg_if(measurements.time_to_initial_display,release,com.example.vu.android@2.10.3+42)': 2000,
  290. 'avg_if(measurements.time_to_full_display,release,com.example.vu.android@2.10.5)': 3000,
  291. 'avg_if(measurements.time_to_full_display,release,com.example.vu.android@2.10.3+42)': 4000,
  292. 'count()': 20,
  293. },
  294. ],
  295. },
  296. match: [
  297. MockApiClient.matchQuery({referrer: 'api.starfish.mobile-screen-totals'}),
  298. ],
  299. });
  300. render(<ScreenLoadSpans />, {organization});
  301. await waitFor(() => {
  302. expect(eventsMock).toHaveBeenCalled();
  303. });
  304. const blocks = [
  305. {header: 'TTID (R1)', value: '1.00s'},
  306. {header: 'TTID (R2)', value: '2.00s'},
  307. {header: 'TTFD (R1)', value: '3.00s'},
  308. {header: 'TTFD (R2)', value: '4.00s'},
  309. {header: 'Count', value: '20'},
  310. ];
  311. for (const block of blocks) {
  312. const blockEl = screen.getByRole('heading', {name: block.header}).closest('div');
  313. await within(blockEl!).findByText(block.value);
  314. }
  315. });
  316. });
  317. });