| /* 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 objectEmulatingUndefined === "function") { |
| var falsyObject = objectEmulatingUndefined(); |
| 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); |