index.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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({features: ['insights-initial-modules']});
  108. mockResponses(organization, project);
  109. localStorage.clear();
  110. browserHistory.push = jest.fn();
  111. eventsMock = MockApiClient.addMockResponse({
  112. url: `/organizations/${organization.slug}/events/`,
  113. });
  114. eventsStatsMock = MockApiClient.addMockResponse({
  115. url: `/organizations/${organization.slug}/events-stats/`,
  116. });
  117. });
  118. afterEach(function () {
  119. MockApiClient.clearMockResponses();
  120. jest.clearAllMocks();
  121. });
  122. it('appends os.name filter for react native projects', async function () {
  123. render(<ScreenLoadSpans />, {organization});
  124. // Event samples
  125. await waitFor(() => {
  126. expect(eventsMock).toHaveBeenCalledWith(
  127. expect.anything(),
  128. expect.objectContaining({
  129. query: expect.objectContaining({
  130. dataset: 'discover',
  131. query:
  132. 'transaction.op:ui.load transaction:MainActivity release:com.example.vu.android@2.10.5 os.name:Android',
  133. }),
  134. })
  135. );
  136. });
  137. await waitFor(() => {
  138. expect(eventsMock).toHaveBeenCalledWith(
  139. expect.anything(),
  140. expect.objectContaining({
  141. query: expect.objectContaining({
  142. dataset: 'discover',
  143. query:
  144. 'transaction.op:ui.load transaction:MainActivity release:com.example.vu.android@2.10.3+42 os.name:Android',
  145. }),
  146. })
  147. );
  148. });
  149. // Span Table
  150. await waitFor(() => {
  151. expect(eventsMock).toHaveBeenCalledWith(
  152. expect.anything(),
  153. expect.objectContaining({
  154. query: expect.objectContaining({
  155. dataset: 'spansMetrics',
  156. query:
  157. '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]',
  158. }),
  159. })
  160. );
  161. });
  162. // Chart
  163. await waitFor(() => {
  164. expect(eventsMock).toHaveBeenCalledWith(
  165. expect.anything(),
  166. expect.objectContaining({
  167. query: expect.objectContaining({
  168. dataset: 'metrics',
  169. query:
  170. '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]',
  171. }),
  172. })
  173. );
  174. });
  175. await waitFor(() => {
  176. expect(eventsStatsMock).toHaveBeenCalledWith(
  177. expect.anything(),
  178. expect.objectContaining({
  179. query: expect.objectContaining({
  180. dataset: 'metrics',
  181. query:
  182. '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]',
  183. }),
  184. })
  185. );
  186. });
  187. });
  188. });
  189. describe('Native Project', function () {
  190. let eventsMock;
  191. let eventsStatsMock;
  192. let organization;
  193. beforeEach(function () {
  194. const project = ProjectFixture({platform: 'android'});
  195. organization = OrganizationFixture({features: ['insights-initial-modules']});
  196. mockResponses(organization, project);
  197. localStorage.clear();
  198. browserHistory.push = jest.fn();
  199. eventsMock = MockApiClient.addMockResponse({
  200. url: `/organizations/${organization.slug}/events/`,
  201. });
  202. eventsStatsMock = MockApiClient.addMockResponse({
  203. url: `/organizations/${organization.slug}/events-stats/`,
  204. });
  205. });
  206. afterEach(function () {
  207. MockApiClient.clearMockResponses();
  208. jest.clearAllMocks();
  209. });
  210. it('does not append os.name filter for native projects', async function () {
  211. render(<ScreenLoadSpans />, {organization});
  212. // Event samples
  213. await waitFor(() => {
  214. expect(eventsMock).toHaveBeenCalledWith(
  215. expect.anything(),
  216. expect.objectContaining({
  217. query: expect.objectContaining({
  218. dataset: 'discover',
  219. query:
  220. 'transaction.op:ui.load transaction:MainActivity release:com.example.vu.android@2.10.5',
  221. }),
  222. })
  223. );
  224. });
  225. await waitFor(() => {
  226. expect(eventsMock).toHaveBeenCalledWith(
  227. expect.anything(),
  228. expect.objectContaining({
  229. query: expect.objectContaining({
  230. dataset: 'discover',
  231. query:
  232. 'transaction.op:ui.load transaction:MainActivity release:com.example.vu.android@2.10.3+42',
  233. }),
  234. })
  235. );
  236. });
  237. // Span Table
  238. await waitFor(() => {
  239. expect(eventsMock).toHaveBeenCalledWith(
  240. expect.anything(),
  241. expect.objectContaining({
  242. query: expect.objectContaining({
  243. dataset: 'spansMetrics',
  244. query:
  245. '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]',
  246. }),
  247. })
  248. );
  249. });
  250. // Chart
  251. await waitFor(() => {
  252. expect(eventsMock).toHaveBeenCalledWith(
  253. expect.anything(),
  254. expect.objectContaining({
  255. query: expect.objectContaining({
  256. dataset: 'metrics',
  257. query:
  258. '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]',
  259. }),
  260. })
  261. );
  262. });
  263. await waitFor(() => {
  264. expect(eventsStatsMock).toHaveBeenCalledWith(
  265. expect.anything(),
  266. expect.objectContaining({
  267. query: expect.objectContaining({
  268. dataset: 'metrics',
  269. query:
  270. '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]',
  271. }),
  272. })
  273. );
  274. });
  275. });
  276. it('renders the top level metrics data correctly', async function () {
  277. eventsMock = MockApiClient.addMockResponse({
  278. url: `/organizations/${organization.slug}/events/`,
  279. body: {
  280. data: [
  281. {
  282. 'avg_if(measurements.time_to_initial_display,release,com.example.vu.android@2.10.5)': 1000,
  283. 'avg_if(measurements.time_to_initial_display,release,com.example.vu.android@2.10.3+42)': 2000,
  284. 'avg_if(measurements.time_to_full_display,release,com.example.vu.android@2.10.5)': 3000,
  285. 'avg_if(measurements.time_to_full_display,release,com.example.vu.android@2.10.3+42)': 4000,
  286. 'count()': 20,
  287. },
  288. ],
  289. },
  290. match: [
  291. MockApiClient.matchQuery({referrer: 'api.starfish.mobile-screen-totals'}),
  292. ],
  293. });
  294. render(<ScreenLoadSpans />, {organization});
  295. await waitFor(() => {
  296. expect(eventsMock).toHaveBeenCalled();
  297. });
  298. const blocks = [
  299. {header: 'Avg TTID (R1)', value: '1.00s'},
  300. {header: 'Avg TTID (R2)', value: '2.00s'},
  301. {header: 'Avg TTFD (R1)', value: '3.00s'},
  302. {header: 'Avg TTFD (R2)', value: '4.00s'},
  303. {header: 'Total Count', value: '20'},
  304. ];
  305. for (const block of blocks) {
  306. const blockEl = screen.getByRole('heading', {name: block.header}).closest('div');
  307. await within(blockEl!).findByText(block.value);
  308. }
  309. });
  310. });
  311. });