index.spec.tsx 11 KB

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