-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Expand file tree
/
Copy pathstructured-clone-blob-file.test.ts
More file actions
298 lines (248 loc) · 10.3 KB
/
structured-clone-blob-file.test.ts
File metadata and controls
298 lines (248 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
import { describe, expect, test } from "bun:test";
describe("structuredClone with Blob and File", () => {
describe("Blob structured clone", () => {
test("empty Blob", () => {
const blob = new Blob([]);
const cloned = structuredClone(blob);
expect(cloned).toBeInstanceOf(Blob);
expect(cloned.size).toBe(0);
expect(cloned.type).toBe("");
});
test("Blob with text content", async () => {
const blob = new Blob(["hello world"], { type: "text/plain" });
const cloned = structuredClone(blob);
expect(cloned).toBeInstanceOf(Blob);
expect(cloned.size).toBe(11);
// Per WHATWG File API, `text/plain` must NOT be canonicalized to
// `text/plain;charset=utf-8` — see #29257.
expect(cloned.type).toBe("text/plain");
const originalText = await blob.text();
const clonedText = await cloned.text();
expect(clonedText).toBe(originalText);
});
test("Blob with binary content", async () => {
const buffer = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello"
const blob = new Blob([buffer], { type: "application/octet-stream" });
const cloned = structuredClone(blob);
expect(cloned).toBeInstanceOf(Blob);
expect(cloned.size).toBe(5);
expect(cloned.type).toBe("application/octet-stream");
const originalBuffer = await blob.arrayBuffer();
const clonedBuffer = await cloned.arrayBuffer();
expect(new Uint8Array(clonedBuffer)).toEqual(new Uint8Array(originalBuffer));
});
test("nested Blob in object", () => {
const blob = new Blob(["test"], { type: "text/plain" });
const obj = { blob: blob };
const cloned = structuredClone(obj);
expect(cloned).toBeInstanceOf(Object);
expect(cloned.blob).toBeInstanceOf(Blob);
expect(cloned.blob.size).toBe(blob.size);
expect(cloned.blob.type).toBe(blob.type);
});
test("nested Blob in array", () => {
const blob = new Blob(["test"], { type: "text/plain" });
const arr = [blob];
const cloned = structuredClone(arr);
expect(cloned).toBeInstanceOf(Array);
expect(cloned[0]).toBeInstanceOf(Blob);
expect(cloned[0].size).toBe(blob.size);
expect(cloned[0].type).toBe(blob.type);
});
test("multiple Blobs in object", () => {
const blob1 = new Blob(["hello"], { type: "text/plain" });
const blob2 = new Blob(["world"], { type: "text/html" });
const obj = { first: blob1, second: blob2 };
const cloned = structuredClone(obj);
expect(cloned.first).toBeInstanceOf(Blob);
expect(cloned.first.size).toBe(5);
expect(cloned.first.type).toBe("text/plain");
expect(cloned.second).toBeInstanceOf(Blob);
expect(cloned.second.size).toBe(5);
expect(cloned.second.type).toBe("text/html");
});
test("deeply nested Blob", () => {
const blob = new Blob(["deep"], { type: "text/plain" });
const obj = { level1: { level2: { level3: { blob: blob } } } };
const cloned = structuredClone(obj);
expect(cloned.level1.level2.level3.blob).toBeInstanceOf(Blob);
expect(cloned.level1.level2.level3.blob.size).toBe(blob.size);
expect(cloned.level1.level2.level3.blob.type).toBe(blob.type);
});
});
describe("File structured clone", () => {
test("File with basic properties", () => {
const file = new File(["content"], "test.txt", {
type: "text/plain",
lastModified: 1234567890000,
});
const cloned = structuredClone(file);
expect(cloned).toBeInstanceOf(File);
expect(cloned.name).toBe("test.txt");
expect(cloned.size).toBe(7);
expect(cloned.type).toBe("text/plain");
expect(cloned.lastModified).toBe(1234567890000);
});
test("File without lastModified", () => {
const file = new File(["content"], "test.txt", { type: "text/plain" });
const cloned = structuredClone(file);
expect(cloned).toBeInstanceOf(File);
expect(cloned.name).toBe("test.txt");
expect(cloned.size).toBe(7);
expect(cloned.type).toBe("text/plain");
expect(cloned.lastModified).toBeGreaterThan(0);
});
test("empty File", () => {
const file = new File([], "empty.txt");
const cloned = structuredClone(file);
expect(cloned).toBeInstanceOf(File);
expect(cloned.name).toBe("empty.txt");
expect(cloned.size).toBe(0);
expect(cloned.type).toBe("");
});
test("nested File in object", () => {
const file = new File(["test"], "test.txt", { type: "text/plain" });
const obj = { file: file };
const cloned = structuredClone(obj);
expect(cloned.file).toBeInstanceOf(File);
expect(cloned.file.name).toBe("test.txt");
expect(cloned.file.size).toBe(4);
expect(cloned.file.type).toBe("text/plain");
});
test("multiple Files in object", () => {
const file1 = new File(["hello"], "hello.txt", { type: "text/plain" });
const file2 = new File(["world"], "world.html", { type: "text/html" });
const obj = { txt: file1, html: file2 };
const cloned = structuredClone(obj);
expect(cloned.txt).toBeInstanceOf(File);
expect(cloned.txt.name).toBe("hello.txt");
expect(cloned.txt.type).toBe("text/plain");
expect(cloned.html).toBeInstanceOf(File);
expect(cloned.html.name).toBe("world.html");
expect(cloned.html.type).toBe("text/html");
});
});
describe("Mixed Blob and File structured clone", () => {
test("Blob and File together", () => {
const blob = new Blob(["blob content"], { type: "text/plain" });
const file = new File(["file content"], "test.txt", { type: "text/plain" });
const obj = { blob: blob, file: file };
const cloned = structuredClone(obj);
expect(cloned.blob).toBeInstanceOf(Blob);
expect(cloned.blob.size).toBe(12);
expect(cloned.blob.type).toBe("text/plain");
expect(cloned.file).toBeInstanceOf(File);
expect(cloned.file.name).toBe("test.txt");
expect(cloned.file.size).toBe(12);
expect(cloned.file.type).toBe("text/plain");
});
test("array with mixed Blob and File", () => {
const blob = new Blob(["blob"], { type: "text/plain" });
const file = new File(["file"], "test.txt", { type: "text/plain" });
const arr = [blob, file];
const cloned = structuredClone(arr);
expect(cloned).toBeInstanceOf(Array);
expect(cloned.length).toBe(2);
expect(cloned[0]).toBeInstanceOf(Blob);
expect(cloned[0].size).toBe(4);
expect(cloned[1]).toBeInstanceOf(File);
expect(cloned[1].name).toBe("test.txt");
expect(cloned[1].size).toBe(4);
});
test("complex nested structure with Blobs and Files", () => {
const blob = new Blob(["blob data"], { type: "text/plain" });
const file = new File(["file data"], "data.txt", { type: "text/plain" });
const complex = {
metadata: { timestamp: Date.now() },
content: {
blob: blob,
files: [file, new File(["another"], "other.txt")],
},
};
const cloned = structuredClone(complex);
expect(cloned.metadata.timestamp).toBe(complex.metadata.timestamp);
expect(cloned.content.blob).toBeInstanceOf(Blob);
expect(cloned.content.blob.size).toBe(9);
expect(cloned.content.files).toBeInstanceOf(Array);
expect(cloned.content.files[0]).toBeInstanceOf(File);
expect(cloned.content.files[0].name).toBe("data.txt");
expect(cloned.content.files[1].name).toBe("other.txt");
});
});
describe("Edge cases with empty data", () => {
test("Blob with empty data", () => {
const blob = new Blob([]);
const cloned = structuredClone(blob);
expect(cloned).toBeInstanceOf(Blob);
expect(cloned.size).toBe(0);
expect(cloned.type).toBe("");
});
test("nested Blob with empty data in object", () => {
const blob = new Blob([]);
const obj = { emptyBlob: blob };
const cloned = structuredClone(obj);
expect(cloned.emptyBlob).toBeInstanceOf(Blob);
expect(cloned.emptyBlob.size).toBe(0);
expect(cloned.emptyBlob.type).toBe("");
});
test("File with empty data", () => {
const file = new File([], "empty.txt");
const cloned = structuredClone(file);
expect(cloned).toBeInstanceOf(File);
expect(cloned.name).toBe("empty.txt");
expect(cloned.size).toBe(0);
expect(cloned.type).toBe("");
});
test("nested File with empty data in object", () => {
const file = new File([], "empty.txt");
const obj = { emptyFile: file };
const cloned = structuredClone(obj);
expect(cloned.emptyFile).toBeInstanceOf(File);
expect(cloned.emptyFile.name).toBe("empty.txt");
expect(cloned.emptyFile.size).toBe(0);
expect(cloned.emptyFile.type).toBe("");
});
test("File with empty data and empty name", () => {
const file = new File([], "");
const cloned = structuredClone(file);
expect(cloned).toBeInstanceOf(File);
expect(cloned.name).toBe("");
expect(cloned.size).toBe(0);
expect(cloned.type).toBe("");
});
test("nested File with empty data and empty name in object", () => {
const file = new File([], "");
const obj = { emptyFile: file };
const cloned = structuredClone(obj);
expect(cloned.emptyFile).toBeInstanceOf(File);
expect(cloned.emptyFile.name).toBe("");
expect(cloned.emptyFile.size).toBe(0);
expect(cloned.emptyFile.type).toBe("");
});
});
describe("Regression tests for issue 20596", () => {
test("original issue case - object with File and Blob", () => {
const clone = structuredClone({
file: new File([], "example.txt"),
blob: new Blob([]),
});
expect(clone).toHaveProperty("file");
expect(clone).toHaveProperty("blob");
expect(clone.file).toBeInstanceOf(File);
expect(clone.blob).toBeInstanceOf(Blob);
expect(clone.file.name).toBe("example.txt");
});
test("single nested Blob should not throw", () => {
const blob = new Blob(["test"]);
const obj = { blob: blob };
const cloned = structuredClone(obj);
expect(cloned.blob).toBeInstanceOf(Blob);
});
test("single nested File should not throw", () => {
const file = new File(["test"], "test.txt");
const obj = { file: file };
const cloned = structuredClone(obj);
expect(cloned.file).toBeInstanceOf(File);
});
});
});