content.spec.tsx 13 KB

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