@rozenite/network-activity-plugin 1.8.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/devtools/App.html +2 -2
  3. package/dist/devtools/assets/{App-m6xge0az.css → App-CUXU0mup.css} +152 -2
  4. package/dist/devtools/assets/{App-B3xlUjs6.js → App-DsimzJvx.js} +6833 -966
  5. package/dist/react-native/chunks/boot-recording.cjs +156 -28
  6. package/dist/react-native/chunks/boot-recording.js +156 -28
  7. package/dist/react-native/chunks/get-nitro-module.cjs +12 -0
  8. package/dist/react-native/chunks/get-nitro-module.js +13 -0
  9. package/dist/react-native/chunks/useNetworkActivityDevTools.require.cjs +20 -1
  10. package/dist/react-native/chunks/useNetworkActivityDevTools.require.js +20 -1
  11. package/dist/react-native/index.d.ts +39 -3
  12. package/dist/rozenite.json +1 -1
  13. package/dist/sdk/index.d.ts +37 -1
  14. package/package.json +12 -7
  15. package/src/react-native/agent/use-network-activity-agent-tools.ts +22 -4
  16. package/src/react-native/http/__tests__/http-utils.test.ts +228 -0
  17. package/src/react-native/http/http-utils.ts +209 -25
  18. package/src/react-native/network-inspector.ts +2 -2
  19. package/src/react-native/nitro-fetch/get-nitro-module.ts +13 -0
  20. package/src/react-native/nitro-fetch/nitro-network-inspector.ts +10 -11
  21. package/src/shared/http-events.ts +40 -1
  22. package/src/ui/components/CodeBlock.tsx +45 -1
  23. package/src/ui/components/FilterBar.tsx +366 -58
  24. package/src/ui/components/HexView.tsx +54 -0
  25. package/src/ui/components/MetadataCard.tsx +95 -0
  26. package/src/ui/components/RequestList.tsx +192 -34
  27. package/src/ui/components/SidePanel.tsx +42 -1
  28. package/src/ui/components/ViewToggle.tsx +44 -0
  29. package/src/ui/components/XmlTree.tsx +160 -0
  30. package/src/ui/components/__tests__/CodeBlock.test.tsx +89 -0
  31. package/src/ui/components/__tests__/HexView.test.tsx +41 -0
  32. package/src/ui/components/__tests__/MetadataCard.test.tsx +107 -0
  33. package/src/ui/components/__tests__/ViewToggle.test.tsx +80 -0
  34. package/src/ui/components/__tests__/XmlTree.test.tsx +149 -0
  35. package/src/ui/response-renderers/__tests__/binary-too-large.test.tsx +56 -0
  36. package/src/ui/response-renderers/__tests__/binary.test.tsx +96 -0
  37. package/src/ui/response-renderers/__tests__/dispatch.test.ts +124 -0
  38. package/src/ui/response-renderers/__tests__/html.test.tsx +101 -0
  39. package/src/ui/response-renderers/__tests__/image.test.tsx +73 -0
  40. package/src/ui/response-renderers/__tests__/json.test.tsx +95 -0
  41. package/src/ui/response-renderers/__tests__/svg.test.tsx +46 -0
  42. package/src/ui/response-renderers/__tests__/xml.test.tsx +100 -0
  43. package/src/ui/response-renderers/binary-too-large.tsx +36 -0
  44. package/src/ui/response-renderers/binary.tsx +31 -0
  45. package/src/ui/response-renderers/empty.tsx +14 -0
  46. package/src/ui/response-renderers/html.tsx +36 -0
  47. package/src/ui/response-renderers/image.tsx +37 -0
  48. package/src/ui/response-renderers/index.ts +55 -0
  49. package/src/ui/response-renderers/json.tsx +40 -0
  50. package/src/ui/response-renderers/svg.tsx +27 -0
  51. package/src/ui/response-renderers/text-fallback.tsx +14 -0
  52. package/src/ui/response-renderers/types.ts +38 -0
  53. package/src/ui/response-renderers/unknown.tsx +18 -0
  54. package/src/ui/response-renderers/xml.tsx +46 -0
  55. package/src/ui/state/derived.ts +12 -0
  56. package/src/ui/state/model.ts +6 -1
  57. package/src/ui/state/store.ts +39 -2
  58. package/src/ui/tabs/InitiatorTab.tsx +230 -0
  59. package/src/ui/tabs/ResponseTab.tsx +80 -96
  60. package/src/ui/tabs/__tests__/ResponseTab.test.tsx +102 -0
  61. package/src/ui/utils/__tests__/download.test.ts +115 -0
  62. package/src/ui/utils/__tests__/hex.test.ts +84 -0
  63. package/src/ui/utils/__tests__/symbolication.test.ts +207 -0
  64. package/src/ui/utils/download.ts +154 -0
  65. package/src/ui/utils/hex.ts +59 -0
  66. package/src/ui/utils/initiator.ts +136 -0
  67. package/src/ui/utils/symbolication.ts +248 -0
  68. package/src/ui/views/InspectorView.tsx +8 -5
  69. package/src/utils/__tests__/getContentTypeMimeType.test.ts +65 -0
  70. package/src/utils/getContentTypeMimeType.ts +28 -0
  71. package/vite.config.ts +9 -1
  72. package/vitest.setup.ts +31 -0
@@ -63,7 +63,7 @@ declare type HttpEventMap = {
63
63
  };
64
64
  'response-body': {
65
65
  requestId: RequestId;
66
- body: string | null;
66
+ body: ResponseBody;
67
67
  };
68
68
  'set-overrides': {
69
69
  overrides: [string, RequestOverride][];
@@ -76,9 +76,37 @@ declare type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
76
76
 
77
77
  declare type Initiator = {
78
78
  type: string;
79
+ symbolicationStatus?: 'pending' | 'complete' | 'failed' | 'unavailable';
80
+ symbolicationError?: string;
81
+ functionName?: string;
79
82
  url?: string;
80
83
  lineNumber?: number;
81
84
  columnNumber?: number;
85
+ generatedUrl?: string;
86
+ generatedLineNumber?: number;
87
+ generatedColumnNumber?: number;
88
+ codeFrame?: InitiatorCodeFrame | null;
89
+ stack?: InitiatorStackFrame[];
90
+ };
91
+
92
+ declare type InitiatorCodeFrame = {
93
+ content: string;
94
+ fileName: string;
95
+ location?: {
96
+ row: number;
97
+ column: number;
98
+ } | null;
99
+ };
100
+
101
+ declare type InitiatorStackFrame = {
102
+ functionName?: string;
103
+ url?: string;
104
+ lineNumber?: number;
105
+ columnNumber?: number;
106
+ generatedUrl?: string;
107
+ generatedLineNumber?: number;
108
+ generatedColumnNumber?: number;
109
+ isCollapsed?: boolean;
82
110
  };
83
111
 
84
112
  declare type InspectorsConfig = {
@@ -171,6 +199,14 @@ declare type Response_2 = {
171
199
  responseTime: Timestamp;
172
200
  };
173
201
 
202
+ declare type ResponseBody = string | {
203
+ kind: 'binary';
204
+ base64: string;
205
+ } | {
206
+ kind: 'binary-too-large';
207
+ size: number;
208
+ } | null;
209
+
174
210
  declare type SSECloseEvent = {
175
211
  type: 'sse-close';
176
212
  requestId: SSERequestId;
@@ -216,7 +252,7 @@ declare type SSERequestId = string;
216
252
 
217
253
  declare type Timestamp = number;
218
254
 
219
- export declare let useNetworkActivityDevTools: useNetworkActivityDevTools_2;
255
+ export declare let useNetworkActivityDevTools: typeof useNetworkActivityDevTools_2;
220
256
 
221
257
  declare const useNetworkActivityDevTools_2: (config?: NetworkActivityDevToolsConfig) => RozeniteDevToolsClient<NetworkActivityEventMap> | null;
222
258
 
@@ -298,7 +334,7 @@ declare type WebSocketOpenEvent = {
298
334
  source?: NetworkEventSource;
299
335
  };
300
336
 
301
- export declare let withOnBootNetworkActivityRecording: withOnBootNetworkActivityRecording_2;
337
+ export declare let withOnBootNetworkActivityRecording: typeof withOnBootNetworkActivityRecording_2;
302
338
 
303
339
  /**
304
340
  * Enable network activity recording during app boot, before DevTools connects.
@@ -1 +1 @@
1
- {"name":"@rozenite/network-activity-plugin","version":"1.8.1","description":"Network Activity for Rozenite.","panels":[{"name":"Network Activity","source":"/devtools/App.html"}]}
1
+ {"name":"@rozenite/network-activity-plugin","version":"1.10.0","description":"Network Activity for Rozenite.","panels":[{"name":"Network Activity","source":"/devtools/App.html"}]}
@@ -402,7 +402,7 @@ declare type HttpEventMap = {
402
402
  };
403
403
  'response-body': {
404
404
  requestId: RequestId;
405
- body: string | null;
405
+ body: ResponseBody;
406
406
  };
407
407
  'set-overrides': {
408
408
  overrides: [string, RequestOverride][];
@@ -415,9 +415,37 @@ export declare type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | '
415
415
 
416
416
  export declare type Initiator = {
417
417
  type: string;
418
+ symbolicationStatus?: 'pending' | 'complete' | 'failed' | 'unavailable';
419
+ symbolicationError?: string;
420
+ functionName?: string;
418
421
  url?: string;
419
422
  lineNumber?: number;
420
423
  columnNumber?: number;
424
+ generatedUrl?: string;
425
+ generatedLineNumber?: number;
426
+ generatedColumnNumber?: number;
427
+ codeFrame?: InitiatorCodeFrame | null;
428
+ stack?: InitiatorStackFrame[];
429
+ };
430
+
431
+ declare type InitiatorCodeFrame = {
432
+ content: string;
433
+ fileName: string;
434
+ location?: {
435
+ row: number;
436
+ column: number;
437
+ } | null;
438
+ };
439
+
440
+ declare type InitiatorStackFrame = {
441
+ functionName?: string;
442
+ url?: string;
443
+ lineNumber?: number;
444
+ columnNumber?: number;
445
+ generatedUrl?: string;
446
+ generatedLineNumber?: number;
447
+ generatedColumnNumber?: number;
448
+ isCollapsed?: boolean;
421
449
  };
422
450
 
423
451
  export declare const NETWORK_ACTIVITY_AGENT_PLUGIN_ID = "@rozenite/network-activity-plugin";
@@ -1070,6 +1098,14 @@ declare type Response_2 = {
1070
1098
  };
1071
1099
  export { Response_2 as Response }
1072
1100
 
1101
+ declare type ResponseBody = string | {
1102
+ kind: 'binary';
1103
+ base64: string;
1104
+ } | {
1105
+ kind: 'binary-too-large';
1106
+ size: number;
1107
+ } | null;
1108
+
1073
1109
  declare type SSEAgentMessage = {
1074
1110
  id: string;
1075
1111
  type: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/network-activity-plugin",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "description": "Network Activity for Rozenite.",
5
5
  "type": "module",
6
6
  "main": "./dist/react-native/index.cjs",
@@ -16,9 +16,9 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "nanoevents": "^9.1.0",
19
- "@rozenite/agent-shared": "1.8.1",
20
- "@rozenite/agent-bridge": "1.8.1",
21
- "@rozenite/plugin-bridge": "1.8.1"
19
+ "@rozenite/agent-shared": "1.10.0",
20
+ "@rozenite/agent-bridge": "1.10.0",
21
+ "@rozenite/plugin-bridge": "1.10.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@floating-ui/react": "^0.26.0",
@@ -29,6 +29,9 @@
29
29
  "@radix-ui/react-tabs": "^1.1.12",
30
30
  "@tanstack/react-table": "^8.21.3",
31
31
  "@tanstack/react-virtual": "^3.0.0",
32
+ "@testing-library/dom": "^10.4.0",
33
+ "@testing-library/jest-dom": "^6.6.3",
34
+ "@testing-library/react": "^16.1.0",
32
35
  "@types/react": "~19.2.2",
33
36
  "@types/react-dom": "~19.1.7",
34
37
  "autoprefixer": "^10.4.21",
@@ -43,14 +46,15 @@
43
46
  "react-native": "0.83.1",
44
47
  "react-native-sse": "^1.2.1",
45
48
  "react-native-web": "^0.21.2",
49
+ "react-virtuoso": "^4.6.0",
46
50
  "tailwind-merge": "^3.3.1",
47
51
  "tailwindcss": "^3.4.17",
48
52
  "tailwindcss-animate": "^1.0.7",
49
53
  "typescript": "~5.9.3",
50
54
  "vite": "^7.3.1",
51
55
  "zustand": "^5.0.6",
52
- "@rozenite/vite-plugin": "1.8.1",
53
- "rozenite": "1.8.1"
56
+ "@rozenite/vite-plugin": "1.10.0",
57
+ "rozenite": "1.10.0"
54
58
  },
55
59
  "peerDependencies": {
56
60
  "react-native-nitro-fetch": "*",
@@ -86,6 +90,7 @@
86
90
  "build": "rozenite build",
87
91
  "dev": "rozenite dev",
88
92
  "typecheck": "tsc -p tsconfig.json --noEmit",
89
- "lint": "eslint ."
93
+ "lint": "eslint .",
94
+ "test": "vitest --run --passWithNoTests"
90
95
  }
91
96
  }
@@ -2,9 +2,7 @@ import { useEffect } from 'react';
2
2
  import { useRozenitePluginAgentTool } from '@rozenite/agent-bridge';
3
3
  import type { NetworkActivityDevToolsClient } from '../../shared/client';
4
4
  import type { NetworkInspector } from '../network-inspector';
5
- import {
6
- getNetworkActivityAgentState,
7
- } from './state';
5
+ import { getNetworkActivityAgentState } from './state';
8
6
  import {
9
7
  NETWORK_ACTIVITY_AGENT_PLUGIN_ID,
10
8
  type NetworkActivityGetResponseBodyResult,
@@ -195,7 +193,9 @@ export const useNetworkActivityAgentTools = ({
195
193
  useRozenitePluginAgentTool({
196
194
  pluginId: NETWORK_ACTIVITY_AGENT_PLUGIN_ID,
197
195
  tool: getResponseBodyTool,
198
- handler: async ({ requestId }): Promise<NetworkActivityGetResponseBodyResult> => {
196
+ handler: async ({
197
+ requestId,
198
+ }): Promise<NetworkActivityGetResponseBodyResult> => {
199
199
  const record = state.getHttpRecord(requestId);
200
200
  if (!record) {
201
201
  throw new Error(`Unknown request "${requestId}"`);
@@ -228,6 +228,24 @@ export const useNetworkActivityAgentTools = ({
228
228
  };
229
229
  }
230
230
 
231
+ if (typeof body !== 'string') {
232
+ if (body.kind === 'binary-too-large') {
233
+ return {
234
+ requestId,
235
+ available: false,
236
+ reason: `Response body exceeded the in-capture size cap (${body.size} bytes).`,
237
+ };
238
+ }
239
+ return {
240
+ requestId,
241
+ available: true,
242
+ body: body.base64,
243
+ base64Encoded: true,
244
+ decoded: false,
245
+ mimeType: record.response?.contentType,
246
+ };
247
+ }
248
+
231
249
  return {
232
250
  requestId,
233
251
  available: true,
@@ -0,0 +1,228 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, expect, it } from 'vitest';
3
+ import { BINARY_CAPTURE_SIZE_CAP, getResponseBody } from '../http-utils';
4
+
5
+ type XHRStubOptions = {
6
+ responseType?: XMLHttpRequestResponseType;
7
+ responseText?: string;
8
+ response?: unknown;
9
+ contentType?: string;
10
+ };
11
+
12
+ const makeXHRStub = ({
13
+ responseType = '',
14
+ responseText = '',
15
+ response = null,
16
+ contentType = '',
17
+ }: XHRStubOptions): XMLHttpRequest =>
18
+ ({
19
+ responseType,
20
+ responseText,
21
+ response,
22
+ getResponseHeader: (name: string) =>
23
+ name.toLowerCase() === 'content-type' ? contentType : null,
24
+ }) as unknown as XMLHttpRequest;
25
+
26
+ describe('getResponseBody', () => {
27
+ it('returns plain text when responseType is empty', async () => {
28
+ const xhr = makeXHRStub({
29
+ responseType: '',
30
+ responseText: 'hello world',
31
+ });
32
+ expect(await getResponseBody(xhr)).toBe('hello world');
33
+ });
34
+
35
+ it('returns plain text when responseType is "text"', async () => {
36
+ const xhr = makeXHRStub({
37
+ responseType: 'text',
38
+ responseText: 'hello world',
39
+ });
40
+ expect(await getResponseBody(xhr)).toBe('hello world');
41
+ });
42
+
43
+ it('stringifies the JSON response when responseType is "json"', async () => {
44
+ const xhr = makeXHRStub({
45
+ responseType: 'json',
46
+ response: { ok: true, n: 1 },
47
+ });
48
+ expect(await getResponseBody(xhr)).toBe('{"ok":true,"n":1}');
49
+ });
50
+
51
+ it('reads a text blob as text', async () => {
52
+ const blob = new Blob(['<p>hello</p>'], { type: 'text/html' });
53
+ const xhr = makeXHRStub({
54
+ responseType: 'blob',
55
+ response: blob,
56
+ contentType: 'text/html; charset=utf-8',
57
+ });
58
+ expect(await getResponseBody(xhr)).toBe('<p>hello</p>');
59
+ });
60
+
61
+ it('reads a JSON blob as text', async () => {
62
+ const blob = new Blob(['{"ok":true}'], { type: 'application/json' });
63
+ const xhr = makeXHRStub({
64
+ responseType: 'blob',
65
+ response: blob,
66
+ contentType: 'application/json',
67
+ });
68
+ expect(await getResponseBody(xhr)).toBe('{"ok":true}');
69
+ });
70
+
71
+ it('routes image/svg+xml through the text path so the source is preserved', async () => {
72
+ const svg = '<svg xmlns="http://www.w3.org/2000/svg"><circle r="5"/></svg>';
73
+ const blob = new Blob([svg], { type: 'image/svg+xml' });
74
+ const xhr = makeXHRStub({
75
+ responseType: 'blob',
76
+ response: blob,
77
+ contentType: 'image/svg+xml',
78
+ });
79
+ expect(await getResponseBody(xhr)).toBe(svg);
80
+ });
81
+
82
+ it('reads application/xml as text', async () => {
83
+ const xml = '<feed><title>Demo</title></feed>';
84
+ const blob = new Blob([xml], { type: 'application/xml' });
85
+ const xhr = makeXHRStub({
86
+ responseType: 'blob',
87
+ response: blob,
88
+ contentType: 'application/xml',
89
+ });
90
+ expect(await getResponseBody(xhr)).toBe(xml);
91
+ });
92
+
93
+ it('reads text/xml as text', async () => {
94
+ const xml = '<root/>';
95
+ const blob = new Blob([xml], { type: 'text/xml' });
96
+ const xhr = makeXHRStub({
97
+ responseType: 'blob',
98
+ response: blob,
99
+ contentType: 'text/xml; charset=utf-8',
100
+ });
101
+ expect(await getResponseBody(xhr)).toBe(xml);
102
+ });
103
+
104
+ it('reads RFC 7303 +xml composite types (Atom, RSS, SOAP) as text', async () => {
105
+ const atom =
106
+ '<feed xmlns="http://www.w3.org/2005/Atom"><entry><title>x</title></entry></feed>';
107
+ const xhr = makeXHRStub({
108
+ responseType: 'blob',
109
+ response: new Blob([atom], { type: 'application/atom+xml' }),
110
+ contentType: 'application/atom+xml; charset=utf-8',
111
+ });
112
+ expect(await getResponseBody(xhr)).toBe(atom);
113
+ });
114
+
115
+ it('returns a binary union variant with base64 for an image blob under the cap', async () => {
116
+ // Three bytes (0x01 0x02 0x03) base64-encodes to "AQID".
117
+ const blob = new Blob([new Uint8Array([1, 2, 3])], { type: 'image/png' });
118
+ const xhr = makeXHRStub({
119
+ responseType: 'blob',
120
+ response: blob,
121
+ contentType: 'image/png',
122
+ });
123
+ const result = await getResponseBody(xhr);
124
+ expect(result).toEqual({ kind: 'binary', base64: 'AQID' });
125
+ });
126
+
127
+ it('returns a binary-too-large variant without shipping bytes when the blob exceeds the cap', async () => {
128
+ // Stub a blob whose .size lies — we only care about the size-check
129
+ // path here, no FileReader.readAsDataURL should be invoked.
130
+ const oversizedBlob = {
131
+ size: BINARY_CAPTURE_SIZE_CAP + 1,
132
+ type: 'image/jpeg',
133
+ } as unknown as Blob;
134
+ const xhr = makeXHRStub({
135
+ responseType: 'blob',
136
+ response: oversizedBlob,
137
+ contentType: 'image/jpeg',
138
+ });
139
+ expect(await getResponseBody(xhr)).toEqual({
140
+ kind: 'binary-too-large',
141
+ size: BINARY_CAPTURE_SIZE_CAP + 1,
142
+ });
143
+ });
144
+
145
+ it('returns a binary union variant for non-image, non-text blob content-types', async () => {
146
+ const blob = new Blob([new Uint8Array([1, 2, 3])], {
147
+ type: 'application/pdf',
148
+ });
149
+ const xhr = makeXHRStub({
150
+ responseType: 'blob',
151
+ response: blob,
152
+ contentType: 'application/pdf',
153
+ });
154
+ expect(await getResponseBody(xhr)).toEqual({
155
+ kind: 'binary',
156
+ base64: 'AQID',
157
+ });
158
+ });
159
+
160
+ it('captures arraybuffer responses as binary', async () => {
161
+ const buffer = new Uint8Array([1, 2, 3]).buffer;
162
+ const xhr = makeXHRStub({
163
+ responseType: 'arraybuffer',
164
+ response: buffer,
165
+ });
166
+ expect(await getResponseBody(xhr)).toEqual({
167
+ kind: 'binary',
168
+ base64: 'AQID',
169
+ });
170
+ });
171
+
172
+ it('chunks large arraybuffer responses without exhausting fromCharCode', async () => {
173
+ // 100 KB of bytes — past the 32 KB chunk boundary used by the
174
+ // base64 encoder, well under the 5 MB cap. Confirms the chunked
175
+ // path produces correct base64 output.
176
+ const size = 100 * 1024;
177
+ const bytes = new Uint8Array(size);
178
+ for (let i = 0; i < size; i++) {
179
+ bytes[i] = i & 0xff;
180
+ }
181
+ const xhr = makeXHRStub({
182
+ responseType: 'arraybuffer',
183
+ response: bytes.buffer,
184
+ });
185
+ const result = await getResponseBody(xhr);
186
+ expect(typeof result === 'object' && result?.kind === 'binary').toBe(true);
187
+ if (typeof result === 'object' && result?.kind === 'binary') {
188
+ // Round-trip: decode the base64 back and compare with the input.
189
+ const binary = atob(result.base64);
190
+ const decoded = new Uint8Array(binary.length);
191
+ for (let i = 0; i < binary.length; i++) {
192
+ decoded[i] = binary.charCodeAt(i);
193
+ }
194
+ expect(decoded.length).toBe(size);
195
+ expect(decoded[0]).toBe(0);
196
+ expect(decoded[size - 1]).toBe((size - 1) & 0xff);
197
+ }
198
+ });
199
+
200
+ it('short-circuits arraybuffer responses above the size cap', async () => {
201
+ // Stub a buffer whose byteLength lies — the cap check should fire
202
+ // before any encoding work happens.
203
+ const oversized = {
204
+ byteLength: BINARY_CAPTURE_SIZE_CAP + 1,
205
+ } as unknown as ArrayBuffer;
206
+ const xhr = makeXHRStub({
207
+ responseType: 'arraybuffer',
208
+ response: oversized,
209
+ });
210
+ expect(await getResponseBody(xhr)).toEqual({
211
+ kind: 'binary-too-large',
212
+ size: BINARY_CAPTURE_SIZE_CAP + 1,
213
+ });
214
+ });
215
+
216
+ it('returns null for an arraybuffer responseType with no payload', async () => {
217
+ const xhr = makeXHRStub({ responseType: 'arraybuffer' });
218
+ expect(await getResponseBody(xhr)).toBeNull();
219
+ });
220
+
221
+ it('returns null for an arraybuffer responseType with an empty buffer', async () => {
222
+ const xhr = makeXHRStub({
223
+ responseType: 'arraybuffer',
224
+ response: new ArrayBuffer(0),
225
+ });
226
+ expect(await getResponseBody(xhr)).toBeNull();
227
+ });
228
+ });