mirror of
https://github.com/hanzoai/core-js.git
synced 2026-09-24 16:58:15 +00:00
508 lines
18 KiB
JavaScript
508 lines
18 KiB
JavaScript
// Originally from: https://github.com/web-platform-tests/wpt/blob/4b35e758e2fc4225368304b02bcec9133965fd1a/IndexedDB/structured-clone.any.js
|
|
// Copyright © web-platform-tests contributors. Available under the 3-Clause BSD License.
|
|
import { GLOBAL, NODE, BUN } from '../helpers/constants.js';
|
|
import { bufferToArray, fromSource } from '../helpers/helpers.js';
|
|
|
|
const { from } = Array;
|
|
const { assign, getPrototypeOf, keys } = Object;
|
|
|
|
QUnit.module('structuredClone', () => {
|
|
QUnit.test('identity', assert => {
|
|
assert.isFunction(structuredClone, 'structuredClone is a function');
|
|
assert.name(structuredClone, 'structuredClone');
|
|
assert.arity(structuredClone, 1);
|
|
if (!NODE) assert.looksNative(structuredClone);
|
|
assert.throws(() => structuredClone(), 'throws without arguments');
|
|
assert.same(structuredClone(1, null), 1, 'null as options');
|
|
assert.same(structuredClone(1, undefined), 1, 'undefined as options');
|
|
});
|
|
|
|
function cloneTest(value, verifyFunc) {
|
|
verifyFunc(value, structuredClone(value));
|
|
}
|
|
|
|
// Specialization of cloneTest() for objects, with common asserts.
|
|
function cloneObjectTest(assert, value, verifyFunc) {
|
|
cloneTest(value, (orig, clone) => {
|
|
assert.notSame(orig, clone, 'clone should have different reference');
|
|
assert.same(typeof clone, 'object', 'clone should be an object');
|
|
// https://github.com/qunitjs/node-qunit/issues/146
|
|
assert.true(getPrototypeOf(orig) === getPrototypeOf(clone), 'clone should have same prototype');
|
|
verifyFunc(orig, clone);
|
|
});
|
|
}
|
|
|
|
// ECMAScript types
|
|
|
|
// Primitive values: Undefined, Null, Boolean, Number, BigInt, String
|
|
const booleans = [false, true];
|
|
const numbers = [
|
|
NaN,
|
|
-Infinity,
|
|
-Number.MAX_VALUE,
|
|
-0xFFFFFFFF,
|
|
-0x80000000,
|
|
-0x7FFFFFFF,
|
|
-1,
|
|
-Number.MIN_VALUE,
|
|
-0,
|
|
0,
|
|
1,
|
|
Number.MIN_VALUE,
|
|
0x7FFFFFFF,
|
|
0x80000000,
|
|
0xFFFFFFFF,
|
|
Number.MAX_VALUE,
|
|
Infinity,
|
|
];
|
|
|
|
const bigints = fromSource(`[
|
|
-12345678901234567890n,
|
|
-1n,
|
|
0n,
|
|
1n,
|
|
12345678901234567890n,
|
|
]`) || [];
|
|
|
|
const strings = [
|
|
'',
|
|
'this is a sample string',
|
|
'null(\0)',
|
|
];
|
|
|
|
QUnit.test('primitives', assert => {
|
|
const primitives = [undefined, null, ...booleans, ...numbers, ...bigints, ...strings];
|
|
|
|
for (const value of primitives) cloneTest(value, (orig, clone) => {
|
|
assert.same(orig, clone, 'primitives should be same after cloned');
|
|
});
|
|
});
|
|
|
|
// "Primitive" Objects (Boolean, Number, BigInt, String)
|
|
QUnit.test('primitive objects', assert => {
|
|
const primitives = [...booleans, ...numbers, ...bigints, ...strings];
|
|
|
|
for (const value of primitives) cloneObjectTest(assert, Object(value), (orig, clone) => {
|
|
assert.same(orig.valueOf(), clone.valueOf(), 'primitive wrappers should have same value');
|
|
});
|
|
});
|
|
|
|
// Dates
|
|
QUnit.test('Date', assert => {
|
|
const dates = [
|
|
new Date(-1e13),
|
|
new Date(-1e12),
|
|
new Date(-1e9),
|
|
new Date(-1e6),
|
|
new Date(-1e3),
|
|
new Date(0),
|
|
new Date(1e3),
|
|
new Date(1e6),
|
|
new Date(1e9),
|
|
new Date(1e12),
|
|
new Date(1e13),
|
|
];
|
|
|
|
for (const date of dates) cloneTest(date, (orig, clone) => {
|
|
assert.notSame(orig, clone);
|
|
assert.same(typeof clone, 'object');
|
|
assert.same(getPrototypeOf(orig), getPrototypeOf(clone));
|
|
assert.same(orig.valueOf(), clone.valueOf());
|
|
});
|
|
});
|
|
|
|
// Regular Expressions
|
|
QUnit.test('RegExp', assert => {
|
|
const regexes = [
|
|
new RegExp(),
|
|
/abc/,
|
|
/abc/g,
|
|
/abc/i,
|
|
/abc/gi,
|
|
];
|
|
|
|
const giuy = fromSource('/abc/giuy');
|
|
if (giuy) regexes.push(giuy);
|
|
|
|
for (const regex of regexes) cloneObjectTest(assert, regex, (orig, clone) => {
|
|
assert.same(orig.toString(), clone.toString(), `regex ${ regex }`);
|
|
});
|
|
});
|
|
|
|
if (fromSource('ArrayBuffer.prototype.slice || DataView')) {
|
|
// ArrayBuffer
|
|
if (typeof Uint8Array == 'function') QUnit.test('ArrayBuffer', assert => { // Crashes
|
|
cloneObjectTest(assert, new Uint8Array([0, 1, 254, 255]).buffer, (orig, clone) => {
|
|
assert.arrayEqual(new Uint8Array(orig), new Uint8Array(clone));
|
|
});
|
|
});
|
|
|
|
// TODO SharedArrayBuffer
|
|
|
|
// Array Buffer Views
|
|
if (typeof Int8Array != 'undefined') {
|
|
QUnit.test('%TypedArray%', assert => {
|
|
const arrays = [
|
|
new Uint8Array([]),
|
|
new Uint8Array([0, 1, 254, 255]),
|
|
new Uint16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]),
|
|
new Uint32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]),
|
|
new Int8Array([0, 1, 254, 255]),
|
|
new Int16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]),
|
|
new Int32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]),
|
|
new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]),
|
|
new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]),
|
|
];
|
|
|
|
if (typeof Uint8ClampedArray != 'undefined') {
|
|
arrays.push(new Uint8ClampedArray([0, 1, 254, 255]));
|
|
}
|
|
|
|
for (const array of arrays) cloneObjectTest(assert, array, (orig, clone) => {
|
|
assert.arrayEqual(orig, clone);
|
|
});
|
|
});
|
|
|
|
if (typeof DataView != 'undefined') QUnit.test('DataView', assert => {
|
|
const array = new Int8Array([1, 2, 3, 4]);
|
|
const view = new DataView(array.buffer);
|
|
|
|
cloneObjectTest(assert, view, (orig, clone) => {
|
|
assert.same(orig.byteLength, clone.byteLength);
|
|
assert.same(orig.byteOffset, clone.byteOffset);
|
|
assert.arrayEqual(new Int8Array(orig.buffer), new Int8Array(clone.buffer));
|
|
});
|
|
});
|
|
}
|
|
|
|
if ('resizable' in ArrayBuffer.prototype) {
|
|
QUnit.test('Resizable ArrayBuffer', assert => {
|
|
const array = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
|
|
let buffer = new ArrayBuffer(8, { maxByteLength: 16 });
|
|
new Int8Array(buffer).set(array);
|
|
let copy = structuredClone(buffer);
|
|
assert.arrayEqual(bufferToArray(copy), array, 'resizable-ab-1');
|
|
assert.true(copy.resizable, 'resizable-ab-1');
|
|
|
|
buffer = new ArrayBuffer(8);
|
|
new Int8Array(buffer).set(array);
|
|
copy = structuredClone(buffer);
|
|
assert.arrayEqual(bufferToArray(copy), array, 'non-resizable-ab-1');
|
|
assert.false(copy.resizable, 'non-resizable-ab-1');
|
|
|
|
buffer = new ArrayBuffer(8, { maxByteLength: 16 });
|
|
let tarray = new Int8Array(buffer);
|
|
tarray.set(array);
|
|
copy = structuredClone(tarray).buffer;
|
|
assert.arrayEqual(bufferToArray(copy), array, 'resizable-ab-2');
|
|
assert.true(copy.resizable, 'resizable-ab-2');
|
|
|
|
buffer = new ArrayBuffer(8);
|
|
tarray = new Int8Array(buffer);
|
|
tarray.set(array);
|
|
copy = structuredClone(tarray).buffer;
|
|
assert.arrayEqual(bufferToArray(copy), array, 'non-resizable-ab-2');
|
|
assert.false(copy.resizable, 'non-resizable-ab-2');
|
|
});
|
|
}
|
|
}
|
|
|
|
// Map
|
|
QUnit.test('Map', assert => {
|
|
cloneObjectTest(assert, new Map([[1, 2], [3, 4]]), (orig, clone) => {
|
|
assert.deepEqual(from(orig.keys()), from(clone.keys()));
|
|
assert.deepEqual(from(orig.values()), from(clone.values()));
|
|
});
|
|
});
|
|
|
|
// Set
|
|
QUnit.test('Set', assert => {
|
|
cloneObjectTest(assert, new Set([1, 2, 3, 4]), (orig, clone) => {
|
|
assert.deepEqual(from(orig.values()), from(clone.values()));
|
|
});
|
|
});
|
|
|
|
// Error
|
|
QUnit.test('Error', assert => {
|
|
const errors = [
|
|
['Error', new Error()],
|
|
['Error', new Error('msg', { cause: 42 })],
|
|
['EvalError', new EvalError()],
|
|
['EvalError', new EvalError('msg', { cause: 42 })],
|
|
['RangeError', new RangeError()],
|
|
['RangeError', new RangeError('msg', { cause: 42 })],
|
|
['ReferenceError', new ReferenceError()],
|
|
['ReferenceError', new ReferenceError('msg', { cause: 42 })],
|
|
['SyntaxError', new SyntaxError()],
|
|
['SyntaxError', new SyntaxError('msg', { cause: 42 })],
|
|
['TypeError', new TypeError()],
|
|
['TypeError', new TypeError('msg', { cause: 42 })],
|
|
['URIError', new URIError()],
|
|
['URIError', new URIError('msg', { cause: 42 })],
|
|
['AggregateError', new AggregateError([1, 2])],
|
|
['AggregateError', new AggregateError([1, 2], 'msg', { cause: 42 })],
|
|
];
|
|
|
|
const compile = fromSource('WebAssembly.CompileError()');
|
|
const link = fromSource('WebAssembly.LinkError()');
|
|
const runtime = fromSource('WebAssembly.RuntimeError()');
|
|
|
|
if (compile && compile.name === 'CompileError') errors.push(['CompileError', compile]);
|
|
if (link && link.name === 'LinkError') errors.push(['LinkError', link]);
|
|
if (runtime && runtime.name === 'RuntimeError') errors.push(['RuntimeError', runtime]);
|
|
|
|
for (const [name, error] of errors) cloneObjectTest(assert, error, (orig, clone) => {
|
|
assert.same(orig.constructor, clone.constructor, `${ name }#constructor`);
|
|
assert.same(orig.name, clone.name, `${ name }#name`);
|
|
assert.same(orig.message, clone.message, `${ name }#message`);
|
|
assert.same(orig.stack, clone.stack, `${ name }#stack`);
|
|
assert.same(orig.cause, clone.cause, `${ name }#cause`);
|
|
assert.deepEqual(orig.errors, clone.errors, `${ name }#errors`);
|
|
});
|
|
});
|
|
|
|
// Arrays
|
|
QUnit.test('Array', assert => {
|
|
const arrays = [
|
|
[],
|
|
[1, 2, 3],
|
|
Array(1),
|
|
assign(
|
|
['foo', 'bar'],
|
|
{ 10: true, 11: false, 20: 123, 21: 456, 30: null }),
|
|
assign(
|
|
['foo', 'bar'],
|
|
{ a: true, b: false, foo: 123, bar: 456, '': null }),
|
|
];
|
|
|
|
for (const array of arrays) cloneObjectTest(assert, array, (orig, clone) => {
|
|
assert.deepEqual(orig, clone, `array content should be same: ${ array }`);
|
|
assert.deepEqual(orig.length, clone.length, `array length should be same: ${ array }`);
|
|
assert.deepEqual(keys(orig), keys(clone), `array key should be same: ${ array }`);
|
|
for (const key of keys(orig)) {
|
|
assert.same(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Objects
|
|
QUnit.test('Object', assert => {
|
|
cloneObjectTest(assert, { foo: true, bar: false }, (orig, clone) => {
|
|
assert.deepEqual(keys(orig), keys(clone));
|
|
for (const key of keys(orig)) {
|
|
assert.same(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
|
|
// [Serializable] Platform objects
|
|
|
|
// Geometry types
|
|
if (typeof DOMMatrix == 'function') {
|
|
QUnit.test('Geometry types, DOMMatrix', assert => {
|
|
cloneObjectTest(assert, new DOMMatrix(), (orig, clone) => {
|
|
for (const key of keys(getPrototypeOf(orig))) {
|
|
assert.same(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (typeof DOMMatrixReadOnly == 'function' && typeof DOMMatrixReadOnly.fromMatrix == 'function') {
|
|
QUnit.test('Geometry types, DOMMatrixReadOnly', assert => {
|
|
cloneObjectTest(assert, new DOMMatrixReadOnly(), (orig, clone) => {
|
|
for (const key of keys(getPrototypeOf(orig))) {
|
|
assert.same(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (typeof DOMPoint == 'function') {
|
|
QUnit.test('Geometry types, DOMPoint', assert => {
|
|
cloneObjectTest(assert, new DOMPoint(1, 2, 3, 4), (orig, clone) => {
|
|
for (const key of keys(getPrototypeOf(orig))) {
|
|
assert.same(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (typeof DOMPointReadOnly == 'function' && typeof DOMPointReadOnly.fromPoint == 'function') {
|
|
QUnit.test('Geometry types, DOMPointReadOnly', assert => {
|
|
cloneObjectTest(assert, new DOMPointReadOnly(1, 2, 3, 4), (orig, clone) => {
|
|
for (const key of keys(getPrototypeOf(orig))) {
|
|
assert.same(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (typeof DOMQuad == 'function' && typeof DOMPoint == 'function') {
|
|
QUnit.test('Geometry types, DOMQuad', assert => {
|
|
cloneObjectTest(assert, new DOMQuad(
|
|
new DOMPoint(1, 2, 3, 4),
|
|
new DOMPoint(2, 2, 3, 4),
|
|
new DOMPoint(1, 3, 3, 4),
|
|
new DOMPoint(1, 2, 4, 4),
|
|
), (orig, clone) => {
|
|
for (const key of keys(getPrototypeOf(orig))) {
|
|
assert.deepEqual(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (fromSource('new DOMRect(1, 2, 3, 4)')) {
|
|
QUnit.test('Geometry types, DOMRect', assert => {
|
|
cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => {
|
|
for (const key of keys(getPrototypeOf(orig))) {
|
|
assert.same(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (typeof DOMRectReadOnly == 'function' && typeof DOMRectReadOnly.fromRect == 'function') {
|
|
QUnit.test('Geometry types, DOMRectReadOnly', assert => {
|
|
cloneObjectTest(assert, new DOMRectReadOnly(1, 2, 3, 4), (orig, clone) => {
|
|
for (const key of keys(getPrototypeOf(orig))) {
|
|
assert.same(orig[key], clone[key], `Property ${ key }`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Safari 8- does not support `{ colorSpace }` option
|
|
if (fromSource('new ImageData(new ImageData(8, 8).data, 8, 8, { colorSpace: new ImageData(8, 8).colorSpace })')) {
|
|
QUnit.test('ImageData', assert => {
|
|
const imageData = new ImageData(8, 8);
|
|
for (let i = 0; i < 256; ++i) {
|
|
imageData.data[i] = i;
|
|
}
|
|
cloneObjectTest(assert, imageData, (orig, clone) => {
|
|
assert.same(orig.width, clone.width);
|
|
assert.same(orig.height, clone.height);
|
|
assert.same(orig.colorSpace, clone.colorSpace);
|
|
assert.arrayEqual(orig.data, clone.data);
|
|
});
|
|
});
|
|
}
|
|
|
|
if (fromSource('new Blob(["test"])')) QUnit.test('Blob', assert => {
|
|
cloneObjectTest(
|
|
assert,
|
|
new Blob(['This is a test.'], { type: 'a/b' }),
|
|
(orig, clone) => {
|
|
assert.same(orig.size, clone.size);
|
|
assert.same(orig.type, clone.type);
|
|
// TODO: async
|
|
// assert.same(await orig.text(), await clone.text());
|
|
});
|
|
});
|
|
|
|
QUnit.test('DOMException', assert => {
|
|
const errors = [
|
|
new DOMException(),
|
|
new DOMException('foo', 'DataCloneError'),
|
|
];
|
|
|
|
for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => {
|
|
assert.same(orig.name, clone.name);
|
|
assert.same(orig.message, clone.message);
|
|
assert.same(orig.code, clone.code);
|
|
assert.same(orig.stack, clone.stack);
|
|
});
|
|
});
|
|
|
|
// https://github.com/oven-sh/bun/issues/11696
|
|
if (!BUN && fromSource('new File(["test"], "foo.txt")')) QUnit.test('File', assert => {
|
|
cloneObjectTest(
|
|
assert,
|
|
new File(['This is a test.'], 'foo.txt', { type: 'c/d' }),
|
|
(orig, clone) => {
|
|
assert.same(orig.size, clone.size);
|
|
assert.same(orig.type, clone.type);
|
|
assert.same(orig.name, clone.name);
|
|
assert.same(orig.lastModified, clone.lastModified);
|
|
// TODO: async
|
|
// assert.same(await orig.text(), await clone.text());
|
|
});
|
|
});
|
|
|
|
// FileList
|
|
if (fromSource('new File(["test"], "foo.txt")') && fromSource('new DataTransfer() && "items" in DataTransfer.prototype')) QUnit.test('FileList', assert => {
|
|
const transfer = new DataTransfer();
|
|
transfer.items.add(new File(['test'], 'foo.txt'));
|
|
cloneObjectTest(
|
|
assert,
|
|
transfer.files,
|
|
(orig, clone) => {
|
|
assert.same(clone.length, 1);
|
|
assert.same(orig[0].size, clone[0].size);
|
|
assert.same(orig[0].type, clone[0].type);
|
|
assert.same(orig[0].name, clone[0].name);
|
|
assert.same(orig[0].lastModified, clone[0].lastModified);
|
|
},
|
|
);
|
|
});
|
|
|
|
// Circular references
|
|
QUnit.test('circular references', assert => {
|
|
const obj = { a: 1 };
|
|
obj.self = obj;
|
|
const clone = structuredClone(obj);
|
|
assert.notSame(obj, clone, 'clone should have different reference');
|
|
assert.same(clone.a, 1, 'primitive property preserved');
|
|
assert.same(clone.self, clone, 'circular reference preserved');
|
|
assert.notSame(clone.self, obj, 'circular reference points to clone, not original');
|
|
|
|
const arr = [1, 2];
|
|
arr.push(arr);
|
|
const arrClone = structuredClone(arr);
|
|
assert.notSame(arr, arrClone, 'array clone different reference');
|
|
assert.same(arrClone[0], 1, 'array element preserved');
|
|
assert.same(arrClone[2], arrClone, 'array circular reference preserved');
|
|
|
|
// Multiple references to same object should be deduplicated
|
|
const shared = { x: 1 };
|
|
const multi = { a: shared, b: shared };
|
|
const multiClone = structuredClone(multi);
|
|
assert.notSame(multi.a, multiClone.a, 'shared object cloned');
|
|
assert.same(multiClone.a, multiClone.b, 'multiple references deduplicated');
|
|
});
|
|
|
|
// Transfer
|
|
if (typeof Uint8Array == 'function') QUnit.test('transfer', assert => {
|
|
const buffer = new ArrayBuffer(4);
|
|
new Uint8Array(buffer).set([1, 2, 3, 4]);
|
|
const clone = structuredClone(buffer, { transfer: [buffer] });
|
|
assert.arrayEqual(new Uint8Array(clone), [1, 2, 3, 4], 'transferred data preserved');
|
|
assert.same(buffer.byteLength, 0, 'source buffer detached');
|
|
assert.same(clone.byteLength, 4, 'clone buffer has correct length');
|
|
});
|
|
|
|
// Non-serializable types
|
|
QUnit.test('Non-serializable types', assert => {
|
|
const nons = [
|
|
function () { return 1; },
|
|
Symbol('desc'),
|
|
GLOBAL,
|
|
];
|
|
|
|
const event = fromSource('new Event("")');
|
|
const port = fromSource('new MessageChannel().port1');
|
|
|
|
// NodeJS events are simple objects
|
|
if (event && !NODE) nons.push(event);
|
|
if (port) nons.push(port);
|
|
|
|
for (const it of nons) {
|
|
// native NodeJS `structuredClone` throws a `TypeError` on transferable non-serializable instead of `DOMException`
|
|
// https://github.com/nodejs/node/issues/40841
|
|
assert.throws(() => structuredClone(it));
|
|
}
|
|
});
|
|
});
|