content.spec.tsx 13 KB

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