/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ function checkDataProperty(object, propertyKey, value, writable, enumerable, configurable) { var desc = Object.getOwnPropertyDescriptor(object, propertyKey); assertEq(desc === undefined, false); assertEq('value' in desc, true); assertEq(desc.value, value); assertEq(desc.writable, writable); assertEq(desc.enumerable, enumerable); assertEq(desc.configurable, configurable); } /* 19.1.2.1 Object.assign ( target, ...sources ) */ assertEq(Object.assign.length, 2); // Basic functionality works with multiple sources function basicMultipleSources() { var a = {}; var b = { bProp : 7 }; var c = { cProp : 8 }; Object.assign(a, b, c); assertEq(a.bProp, 7); assertEq(a.cProp, 8); } basicMultipleSources(); // Basic functionality works with symbols (Bug 1052358) function basicSymbols() { var a = {}; var b = { bProp : 7 }; var aSymbol = Symbol("aSymbol"); b[aSymbol] = 22; Object.assign(a, b); assertEq(a.bProp, 7); assertEq(a[aSymbol], 22); } basicSymbols(); // Calls ToObject() for target, skips null/undefined sources, uses // ToObject(source) otherwise. function testToObject() { assertThrowsInstanceOf(() => Object.assign(null, null), TypeError); assertThrowsInstanceOf(() => Object.assign(), TypeError); assertThrowsInstanceOf(() => Object.assign(null, {}), TypeError); assertEq(Object.assign({}, null) instanceof Object, true); assertEq(Object.assign({}, undefined) instanceof Object, true); // Technically an embedding could have this as extension acting differently // from ours, so a feature-test is inadequate. We can move this subtest // into extensions/ if that ever matters. if (typeof createIsHTMLDDA === "function") { var falsyObject = createIsHTMLDDA(); falsyObject.foo = 7; var obj = Object.assign({}, falsyObject); assertEq(obj instanceof Object, true); assertEq(obj.foo, 7); } assertEq(Object.assign(true, {}) instanceof Boolean, true); assertEq(Object.assign(1, {}) instanceof Number, true); assertEq(Object.assign("string", {}) instanceof String, true); var o = {}; assertEq(Object.assign(o, {}), o); } testToObject(); // Invokes [[OwnPropertyKeys]] on ToObject(source) function testOwnPropertyKeys() { assertThrowsInstanceOf(() => Object.assign(null, new Proxy({}, { getOwnPropertyNames: () => { throw new Error("not called"); } })), TypeError); var ownKeysCalled = false; Object.assign({}, new Proxy({}, { ownKeys: function() { ownKeysCalled = true; return []; } })); assertEq(ownKeysCalled, true); }; testOwnPropertyKeys(); // Ensure correct property traversal function correctPropertyTraversal() { var log = ""; var source = new Proxy({a: 1, b: 2}, { ownKeys: () => ["b", "c", "a"], getOwnPropertyDescriptor: function(t, pk) { log += "#" + pk; return Object.getOwnPropertyDescriptor(t, pk); }, get: function(t, pk, r) { log += "-" + pk; return t[pk]; }, }); Object.assign({}, source); assertEq(log, "#b-b#c#a-a"); } correctPropertyTraversal(); // Only [[Enumerable]] properties are assigned to target function onlyEnumerablePropertiesAssigned() { var source = Object.defineProperties({}, { a: {value: 1, enumerable: true}, b: {value: 2, enumerable: false}, }); var target = Object.assign({}, source); assertEq("a" in target, true); assertEq("b" in target, false); } onlyEnumerablePropertiesAssigned(); // Enumerability is decided on-time, not before main loop (1) function testEnumerabilityDeterminedInLoop1() { var getterCalled = false; var sourceTarget = { get a() { getterCalled = true }, get b() { Object.defineProperty(sourceTarget, "a", {enumerable: false}) }, }; var source = new Proxy(sourceTarget, { ownKeys: () => ["b", "a"] }); Object.assign({}, source); assertEq(getterCalled, false); } testEnumerabilityDeterminedInLoop1(); // Enumerability is decided on-time, not before main loop (2) function testEnumerabilityDeterminedInLoop2() { var getterCalled = false; var sourceTarget = { get a() { getterCalled = true }, get b() { Object.defineProperty(sourceTarget, "a", {enumerable: true}) }, }; var source = new Proxy(sourceTarget, { ownKeys: () => ["b", "a"] }); Object.defineProperty(sourceTarget, "a", {enumerable: false}); Object.assign({}, source); assertEq(getterCalled, true); } testEnumerabilityDeterminedInLoop2(); // Properties are retrieved through Get() and assigned onto // the target as data properties, not in any sense cloned over as descriptors function testPropertiesRetrievedThroughGet() { var getterCalled = false; Object.assign({}, {get a() { getterCalled = true }}); assertEq(getterCalled, true); } testPropertiesRetrievedThroughGet(); // Properties are retrieved through Get() // Properties are assigned through Put() function testPropertiesAssignedThroughPut() { var setterCalled = false; Object.assign({set a(v) { setterCalled = v }}, {a: true}); assertEq(setterCalled, true); } testPropertiesAssignedThroughPut(); // Properties are retrieved through Get() // Properties are assigned through Put(): Existing property attributes are not altered function propertiesAssignedExistingNotAltered() { var source = {a: 1, b: 2, c: 3}; var target = {a: 0, b: 0, c: 0}; Object.defineProperty(target, "a", {enumerable: false}); Object.defineProperty(target, "b", {configurable: false}); Object.defineProperty(target, "c", {enumerable: false, configurable: false}); Object.assign(target, source); checkDataProperty(target, "a", 1, true, false, true); checkDataProperty(target, "b", 2, true, true, false); checkDataProperty(target, "c", 3, true, false, false); } propertiesAssignedExistingNotAltered(); // Properties are retrieved through Get() // Properties are assigned through Put(): Throws TypeError if non-writable function propertiesAssignedTypeErrorNonWritable() { var source = {a: 1}; var target = {a: 0}; Object.defineProperty(target, "a", {writable: false}); assertThrowsInstanceOf(() => Object.assign(target, source), TypeError); checkDataProperty(target, "a", 0, false, true, true); } propertiesAssignedTypeErrorNonWritable(); // Properties are retrieved through Get() // Put() creates standard properties; Property attributes from source are ignored function createsStandardProperties() { var source = {a: 1, b: 2, c: 3, get d() { return 4 }}; Object.defineProperty(source, "b", {writable: false}); Object.defineProperty(source, "c", {configurable: false}); var target = Object.assign({}, source); checkDataProperty(target, "a", 1, true, true, true); checkDataProperty(target, "b", 2, true, true, true); checkDataProperty(target, "c", 3, true, true, true); checkDataProperty(target, "d", 4, true, true, true); } createsStandardProperties(); // Properties created during traversal are not copied function propertiesCreatedDuringTraversalNotCopied() { var source = {get a() { this.b = 2 }}; var target = Object.assign({}, source); assertEq("a" in target, true); assertEq("b" in target, false); } propertiesCreatedDuringTraversalNotCopied(); // Properties deleted during traversal are not copied function testDeletePropertiesNotCopied() { var source = new Proxy({ get a() { delete this.b }, b: 2, }, { getOwnPropertyNames: () => ["a", "b"] }); var target = Object.assign({}, source); assertEq("a" in target, true); assertEq("b" in target, false); } testDeletePropertiesNotCopied(); function testDeletionExposingShadowedProperty() { var srcProto = { b: 42 }; var src = Object.create(srcProto, { a: { enumerable: true, get: function() { delete this.b; } }, b: { value: 2, configurable: true, enumerable: true } }); var source = new Proxy(src, { getOwnPropertyNames: () => ["a", "b"] }); var target = Object.assign({}, source); assertEq("a" in target, true); assertEq("b" in target, false); } testDeletionExposingShadowedProperty(); // Properties first deleted and then recreated during traversal are copied (1) function testDeletedAndRecreatedPropertiesCopied1() { var source = new Proxy({ get a() { delete this.c }, get b() { this.c = 4 }, c: 3, }, { getOwnPropertyNames: () => ["a", "b", "c"] }); var target = Object.assign({}, source); assertEq("a" in target, true); assertEq("b" in target, true); assertEq("c" in target, true); checkDataProperty(target, "c", 4, true, true, true); } testDeletedAndRecreatedPropertiesCopied1(); // Properties first deleted and then recreated during traversal are copied (2) function testDeletedAndRecreatedPropertiesCopied2() { var source = new Proxy({ get a() { delete this.c }, get b() { this.c = 4 }, c: 3, }, { ownKeys: () => ["a", "c", "b"] }); var target = Object.assign({}, source); assertEq("a" in target, true); assertEq("b" in target, true); assertEq("c" in target, false); } testDeletedAndRecreatedPropertiesCopied2(); // String and Symbol valued properties are copied function testStringAndSymbolPropertiesCopied() { var keyA = "str-prop"; var source = {"str-prop": 1}; var target = Object.assign({}, source); checkDataProperty(target, keyA, 1, true, true, true); } testStringAndSymbolPropertiesCopied(); // Intermediate exceptions stop traversal and throw exception function testExceptionsDoNotStopFirstReported1() { var TestError = function TestError() {}; var source = new Proxy({}, { getOwnPropertyDescriptor: function(t, pk) { assertEq(pk, "b"); throw new TestError(); }, ownKeys: () => ["b", "a"] }); assertThrowsInstanceOf(() => Object.assign({}, source), TestError); } testExceptionsDoNotStopFirstReported1(); if (typeof reportCompare == "function") reportCompare(true, true);