content.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. import {render, screen} from 'sentry-test/reactTestingLibrary';
  2. import hydrateSpans from 'sentry/utils/replays/hydrateSpans';
  3. import useProjectSdkNeedsUpdate from 'sentry/utils/useProjectSdkNeedsUpdate';
  4. import NetworkDetailsContent from 'sentry/views/replays/detail/network/details/content';
  5. import type {TabKey} from 'sentry/views/replays/detail/network/details/tabs';
  6. jest.mock('sentry/utils/useProjectSdkNeedsUpdate');
  7. function mockNeedsUpdate(needsUpdate: boolean) {
  8. jest
  9. .mocked(useProjectSdkNeedsUpdate)
  10. .mockReturnValue({isError: false, isFetching: false, needsUpdate});
  11. }
  12. const [
  13. img,
  14. fetchNoDataObj,
  15. fetchUrlSkipped,
  16. fetchBodySkipped,
  17. fetchWithHeaders,
  18. fetchWithRespBody,
  19. ] = hydrateSpans(TestStubs.ReplayRecord(), [
  20. TestStubs.Replay.ResourceFrame({
  21. op: 'resource.img',
  22. startTimestamp: new Date(),
  23. endTimestamp: new Date(),
  24. description: '/static/img/logo.png',
  25. }),
  26. TestStubs.Replay.RequestFrame({
  27. op: 'resource.fetch',
  28. startTimestamp: new Date(),
  29. endTimestamp: new Date(),
  30. description: '/api/0/issues/1234',
  31. }),
  32. TestStubs.Replay.RequestFrame({
  33. op: 'resource.fetch',
  34. startTimestamp: new Date(),
  35. endTimestamp: new Date(),
  36. description: '/api/0/issues/1234',
  37. data: {
  38. method: 'GET',
  39. statusCode: 200,
  40. request: {_meta: {warnings: ['URL_SKIPPED']}, headers: {}},
  41. response: {_meta: {warnings: ['URL_SKIPPED']}, headers: {}},
  42. },
  43. }),
  44. TestStubs.Replay.RequestFrame({
  45. op: 'resource.fetch',
  46. startTimestamp: new Date(),
  47. endTimestamp: new Date(),
  48. description: '/api/0/issues/1234',
  49. data: {
  50. method: 'GET',
  51. statusCode: 200,
  52. request: {
  53. // @ts-expect-error
  54. _meta: {warnings: ['BODY_SKIPPED']},
  55. headers: {accept: 'application/json'},
  56. },
  57. response: {
  58. // @ts-expect-error
  59. _meta: {warnings: ['BODY_SKIPPED']},
  60. headers: {'content-type': 'application/json'},
  61. },
  62. },
  63. }),
  64. TestStubs.Replay.RequestFrame({
  65. op: 'resource.fetch',
  66. startTimestamp: new Date(),
  67. endTimestamp: new Date(),
  68. description: '/api/0/issues/1234',
  69. data: {
  70. method: 'GET',
  71. statusCode: 200,
  72. request: {
  73. _meta: {},
  74. headers: {accept: 'application/json'},
  75. },
  76. response: {
  77. _meta: {},
  78. headers: {'content-type': 'application/json'},
  79. },
  80. },
  81. }),
  82. TestStubs.Replay.RequestFrame({
  83. op: 'resource.fetch',
  84. startTimestamp: new Date(),
  85. endTimestamp: new Date(),
  86. description: '/api/0/issues/1234',
  87. data: {
  88. method: 'GET',
  89. statusCode: 200,
  90. request: {
  91. _meta: {},
  92. headers: {accept: 'application/json'},
  93. },
  94. response: {
  95. _meta: {},
  96. headers: {'content-type': 'application/json'},
  97. body: {success: true},
  98. },
  99. },
  100. }),
  101. ]);
  102. const mockItems = {
  103. img,
  104. fetchNoDataObj,
  105. fetchUrlSkipped,
  106. fetchBodySkipped,
  107. fetchWithHeaders,
  108. fetchWithRespBody,
  109. };
  110. function basicSectionProps() {
  111. return {
  112. projectId: '',
  113. startTimestampMs: new Date('2023-12-24').getTime(),
  114. };
  115. }
  116. function queryScreenState() {
  117. return {
  118. dataSectionHeaders: screen
  119. .queryAllByLabelText('toggle section')
  120. .map(elem => elem.textContent),
  121. isShowingSetup: Boolean(screen.queryByTestId('network-setup-steps')),
  122. isShowingUnsupported: Boolean(screen.queryByTestId('network-op-unsupported')),
  123. };
  124. }
  125. describe('NetworkDetailsContent', () => {
  126. mockNeedsUpdate(false);
  127. describe('Details Tab', () => {
  128. const visibleTab = 'details' as TabKey;
  129. describe('Unsupported Operation', () => {
  130. it.each([
  131. {isSetup: false, itemName: 'img'},
  132. {isSetup: true, itemName: 'img'},
  133. ])(
  134. 'should render the `general` & `unsupported` sections when the span is not FETCH or XHR and isSetup=$isSetup. [$itemName]',
  135. ({isSetup}) => {
  136. render(
  137. <NetworkDetailsContent
  138. {...basicSectionProps()}
  139. isSetup={isSetup}
  140. item={mockItems.img}
  141. visibleTab={visibleTab}
  142. />
  143. );
  144. expect(queryScreenState()).toStrictEqual({
  145. dataSectionHeaders: ['General'],
  146. isShowingSetup: false,
  147. isShowingUnsupported: true,
  148. });
  149. }
  150. );
  151. });
  152. describe('Supported Operation', () => {
  153. it.each([
  154. {isSetup: false, itemName: 'fetchNoDataObj'},
  155. {isSetup: false, itemName: 'fetchUrlSkipped'},
  156. {isSetup: false, itemName: 'fetchBodySkipped'},
  157. {isSetup: false, itemName: 'fetchWithHeaders'},
  158. {isSetup: false, itemName: 'fetchWithRespBody'},
  159. ])(
  160. 'should render the `general` & `setup` sections when isSetup=false, no matter the item. [$itemName]',
  161. ({isSetup, itemName}) => {
  162. render(
  163. <NetworkDetailsContent
  164. {...basicSectionProps()}
  165. isSetup={isSetup}
  166. item={mockItems[itemName]}
  167. visibleTab={visibleTab}
  168. />
  169. );
  170. expect(queryScreenState()).toStrictEqual({
  171. dataSectionHeaders: ['General'],
  172. isShowingSetup: true,
  173. isShowingUnsupported: false,
  174. });
  175. }
  176. );
  177. it.each([
  178. {isSetup: true, itemName: 'fetchNoDataObj'},
  179. {isSetup: true, itemName: 'fetchUrlSkipped'},
  180. ])(
  181. 'should render the `general` & `setup` sections when the item has no data. [$itemName]',
  182. ({isSetup, itemName}) => {
  183. render(
  184. <NetworkDetailsContent
  185. {...basicSectionProps()}
  186. isSetup={isSetup}
  187. item={mockItems[itemName]}
  188. visibleTab={visibleTab}
  189. />
  190. );
  191. expect(queryScreenState()).toStrictEqual({
  192. dataSectionHeaders: ['General'],
  193. isShowingSetup: true,
  194. isShowingUnsupported: false,
  195. });
  196. }
  197. );
  198. it.each([
  199. {isSetup: true, itemName: 'fetchBodySkipped'},
  200. {isSetup: true, itemName: 'fetchWithHeaders'},
  201. {isSetup: true, itemName: 'fetchWithRespBody'},
  202. ])(
  203. 'should render the `general` & two `headers` sections, and always the setup section, when things are setup and the item has some data. [$itemName]',
  204. ({isSetup, itemName}) => {
  205. render(
  206. <NetworkDetailsContent
  207. {...basicSectionProps()}
  208. isSetup={isSetup}
  209. item={mockItems[itemName]}
  210. visibleTab={visibleTab}
  211. />
  212. );
  213. expect(queryScreenState()).toStrictEqual({
  214. dataSectionHeaders: ['General', 'Request Headers', 'Response Headers'],
  215. isShowingUnsupported: false,
  216. isShowingSetup: true,
  217. });
  218. }
  219. );
  220. });
  221. });
  222. describe('Request Tab', () => {
  223. const visibleTab = 'request' as TabKey;
  224. describe('Unsupported Operation', () => {
  225. it.each([
  226. {isSetup: false, itemName: 'img'},
  227. {isSetup: true, itemName: 'img'},
  228. ])(
  229. 'should render the `query params` & `unsupported` sections when the span is not FETCH or XHR and isSetup=$isSetup. [$itemName]',
  230. ({isSetup}) => {
  231. render(
  232. <NetworkDetailsContent
  233. {...basicSectionProps()}
  234. isSetup={isSetup}
  235. item={mockItems.img}
  236. visibleTab={visibleTab}
  237. />
  238. );
  239. expect(queryScreenState()).toStrictEqual({
  240. dataSectionHeaders: ['Query String Parameters'],
  241. isShowingSetup: false,
  242. isShowingUnsupported: true,
  243. });
  244. }
  245. );
  246. });
  247. describe('Supported Operation', () => {
  248. it.each([
  249. {isSetup: false, itemName: 'fetchNoDataObj'},
  250. {isSetup: false, itemName: 'fetchUrlSkipped'},
  251. {isSetup: false, itemName: 'fetchBodySkipped'},
  252. {isSetup: false, itemName: 'fetchWithHeaders'},
  253. {isSetup: false, itemName: 'fetchWithRespBody'},
  254. ])(
  255. 'should render the `query params` & `setup` sections when isSetup is false, no matter the item. [$itemName]',
  256. ({isSetup, itemName}) => {
  257. render(
  258. <NetworkDetailsContent
  259. {...basicSectionProps()}
  260. isSetup={isSetup}
  261. item={mockItems[itemName]}
  262. visibleTab={visibleTab}
  263. />
  264. );
  265. expect(queryScreenState()).toStrictEqual({
  266. dataSectionHeaders: ['Query String Parameters'],
  267. isShowingSetup: true,
  268. isShowingUnsupported: false,
  269. });
  270. }
  271. );
  272. it.each([
  273. {isSetup: true, itemName: 'fetchNoDataObj'},
  274. {isSetup: true, itemName: 'fetchUrlSkipped'},
  275. {isSetup: true, itemName: 'fetchBodySkipped'},
  276. {isSetup: true, itemName: 'fetchWithHeaders'},
  277. ])(
  278. 'should render the `query params` & `setup` sections when the item has no data. [$itemName]',
  279. ({isSetup, itemName}) => {
  280. render(
  281. <NetworkDetailsContent
  282. {...basicSectionProps()}
  283. isSetup={isSetup}
  284. item={mockItems[itemName]}
  285. visibleTab={visibleTab}
  286. />
  287. );
  288. expect(queryScreenState()).toStrictEqual({
  289. dataSectionHeaders: ['Query String Parameters'],
  290. isShowingSetup: true,
  291. isShowingUnsupported: false,
  292. });
  293. }
  294. );
  295. it.each([{isSetup: true, itemName: 'fetchWithRespBody'}])(
  296. 'should render the `query params` & `request payload` sections when things are setup and the item has some data. [$itemName]',
  297. ({isSetup, itemName}) => {
  298. render(
  299. <NetworkDetailsContent
  300. {...basicSectionProps()}
  301. isSetup={isSetup}
  302. item={mockItems[itemName]}
  303. visibleTab={visibleTab}
  304. />
  305. );
  306. expect(queryScreenState()).toStrictEqual({
  307. dataSectionHeaders: ['Query String Parameters', 'Request BodySize: 0 B'],
  308. isShowingUnsupported: false,
  309. isShowingSetup: false,
  310. });
  311. }
  312. );
  313. });
  314. });
  315. describe('Response Tab', () => {
  316. const visibleTab = 'response' as TabKey;
  317. describe('Unsupported Operation', () => {
  318. it.each([
  319. {isSetup: false, itemName: 'img'},
  320. {isSetup: true, itemName: 'img'},
  321. ])(
  322. 'should render the `unsupported` section when the span is not FETCH or XHR and isSetup=$isSetup. [$itemName]',
  323. ({isSetup}) => {
  324. render(
  325. <NetworkDetailsContent
  326. {...basicSectionProps()}
  327. isSetup={isSetup}
  328. item={mockItems.img}
  329. visibleTab={visibleTab}
  330. />
  331. );
  332. expect(queryScreenState()).toStrictEqual({
  333. dataSectionHeaders: [],
  334. isShowingSetup: false,
  335. isShowingUnsupported: true,
  336. });
  337. }
  338. );
  339. });
  340. describe('Supported Operation', () => {
  341. it.each([
  342. {isSetup: false, itemName: 'fetchNoDataObj'},
  343. {isSetup: false, itemName: 'fetchUrlSkipped'},
  344. {isSetup: false, itemName: 'fetchBodySkipped'},
  345. {isSetup: false, itemName: 'fetchWithHeaders'},
  346. {isSetup: false, itemName: 'fetchWithRespBody'},
  347. ])(
  348. 'should render the `setup` section when isSetup is false, no matter the item. [$itemName]',
  349. ({isSetup, itemName}) => {
  350. render(
  351. <NetworkDetailsContent
  352. {...basicSectionProps()}
  353. isSetup={isSetup}
  354. item={mockItems[itemName]}
  355. visibleTab={visibleTab}
  356. />
  357. );
  358. expect(queryScreenState()).toStrictEqual({
  359. dataSectionHeaders: [],
  360. isShowingSetup: true,
  361. isShowingUnsupported: false,
  362. });
  363. }
  364. );
  365. it.each([
  366. {isSetup: true, itemName: 'fetchNoDataObj'},
  367. {isSetup: true, itemName: 'fetchUrlSkipped'},
  368. {isSetup: true, itemName: 'fetchBodySkipped'},
  369. {isSetup: true, itemName: 'fetchWithHeaders'},
  370. ])(
  371. 'should render the `setup` section when the item has no data. [$itemName]',
  372. ({isSetup, itemName}) => {
  373. render(
  374. <NetworkDetailsContent
  375. {...basicSectionProps()}
  376. isSetup={isSetup}
  377. item={mockItems[itemName]}
  378. visibleTab={visibleTab}
  379. />
  380. );
  381. expect(queryScreenState()).toStrictEqual({
  382. dataSectionHeaders: [],
  383. isShowingSetup: true,
  384. isShowingUnsupported: false,
  385. });
  386. }
  387. );
  388. it.each([{isSetup: true, itemName: 'fetchWithRespBody'}])(
  389. 'should render the `response body` section when things are setup and the item has some data. [$itemName]',
  390. ({isSetup, itemName}) => {
  391. render(
  392. <NetworkDetailsContent
  393. {...basicSectionProps()}
  394. isSetup={isSetup}
  395. item={mockItems[itemName]}
  396. visibleTab={visibleTab}
  397. />
  398. );
  399. expect(queryScreenState()).toStrictEqual({
  400. dataSectionHeaders: ['Response BodySize: 0 B'],
  401. isShowingUnsupported: false,
  402. isShowingSetup: false,
  403. });
  404. }
  405. );
  406. });
  407. });
  408. });