Source code
Revision control
Copy as Markdown
Other Tools
// Object spread over sources that CopyDataPropertiesNative can't copy, so
// they take the key-by-key path: getters, indexed properties, and targets
// that already have properties.
function keys(o) {
return Reflect.ownKeys(o).map(String).join(",");
}
function testGetters() {
var from, to;
// A getter can delete a later key.
from = {get a() { delete this.b; return 1; }, b: 2, c: 3};
to = {...from};
assertEq(keys(to), "a,c");
// A getter can make a later key non-enumerable.
from = {
get a() {
Object.defineProperty(this, "b", {enumerable: false});
return 1;
},
b: 2,
c: 3,
};
to = {...from};
assertEq(keys(to), "a,c");
// A getter can replace a later data property with an accessor.
from = {
get a() {
Object.defineProperty(this, "b", {get() { return "new"; }});
return 1;
},
b: 2,
};
to = {...from};
assertEq(to.b, "new");
assertEq(Object.getOwnPropertyDescriptor(to, "b").value, "new");
// Keys added by a getter aren't copied.
from = {get a() { this.z = 26; return 1; }, b: 2};
to = {...from};
assertEq(keys(to), "a,b");
// A getter that throws.
from = {x: 1, get y() { throw "boom"; }, z: 3};
try {
to = {...from};
assertEq(true, false);
} catch (e) {
assertEq(e, "boom");
}
// A setter without a getter copies undefined.
from = {set a(v) { throw "setter called"; }, b: 2};
to = {...from};
assertEq(keys(to), "a,b");
assertEq(to.a, undefined);
assertEq(Object.getOwnPropertyDescriptor(to, "a").writable, true);
// Non-enumerable and inherited getters aren't called.
var proto = {get inherited() { throw "inherited getter called"; }};
from = Object.create(proto);
Object.defineProperty(from, "hidden", {
get() { throw "hidden getter called"; },
enumerable: false,
});
from.own = 1;
Object.defineProperty(from, "g", {get() { return 7; }, enumerable: true});
to = {...from};
assertEq(keys(to), "own,g");
assertEq(to.g, 7);
}
function testIndexed() {
var from, to;
// Dense elements, string keys and a getter together.
from = {b: 1, get g() { return "g"; }};
from[1] = "one";
from[0] = "zero";
to = {...from};
assertEq(keys(to), "0,1,b,g");
assertEq(to[0], "zero");
assertEq(to.g, "g");
// Holes aren't copied.
to = {...[1, , 3]};
assertEq(keys(to), "0,2");
// A non-enumerable element isn't copied.
from = [10, 20, 30];
Object.defineProperty(from, 1, {enumerable: false});
to = {...from};
assertEq(keys(to), "0,2");
// Typed arrays and String objects with extra properties.
from = new Int8Array([1, 2]);
from.extra = "e";
to = {...from};
assertEq(keys(to), "0,1,extra");
assertEq(to[1], 2);
from = new String("ab");
from.extra = "e";
from[5] = "five";
to = {...from};
assertEq(keys(to), "0,1,5,extra");
assertEq(to[1], "b");
assertEq(to[5], "five");
// Arguments objects.
function mapped(a, b) {
delete arguments[0];
return {...arguments};
}
to = mapped(1, 2, 3);
assertEq(keys(to), "1,2");
assertEq(to[2], 3);
function unmapped(a, b) {
"use strict";
arguments.extra = "e";
return {...arguments};
}
to = unmapped(1, 2);
assertEq(keys(to), "0,1,extra");
}
function testNonEmptyTarget() {
var from, to, set;
// A colliding key is overwritten in place.
from = {a: 2, get b() { return 3; }};
to = {c: 0, a: 1, ...from};
assertEq(keys(to), "c,a,b");
assertEq(to.a, 2);
// A colliding accessor on the target becomes a data property.
to = {get a() { return "target"; }, ...from};
assertEq(Object.getOwnPropertyDescriptor(to, "a").value, 2);
assertEq(Object.getOwnPropertyDescriptor(to, "a").get, undefined);
// Colliding integer keys.
from = ["x", "y"];
to = {0: "t0", 5: "t5", ...from};
assertEq(keys(to), "0,1,5");
assertEq(to[0], "x");
// Copying defines, so setters on the prototype don't run.
set = false;
var proto = {set a(v) { set = true; }};
from = {a: 1, get b() { return 2; }};
to = {__proto__: proto, ...from};
assertEq(set, false);
assertEq(Object.getOwnPropertyDescriptor(to, "a").value, 1);
to = {__proto__: proto, x: 0, ...from};
assertEq(set, false);
assertEq(keys(to), "x,a,b");
}
testGetters();
testIndexed();
testNonEmptyTarget();