content.spec.tsx 13 KB

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