assign.js (10476B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 function checkDataProperty(object, propertyKey, value, writable, enumerable, configurable) { 6 var desc = Object.getOwnPropertyDescriptor(object, propertyKey); 7 assertEq(desc === undefined, false); 8 assertEq('value' in desc, true); 9 assertEq(desc.value, value); 10 assertEq(desc.writable, writable); 11 assertEq(desc.enumerable, enumerable); 12 assertEq(desc.configurable, configurable); 13 } 14 15 /* 19.1.2.1 Object.assign ( target, ...sources ) */ 16 assertEq(Object.assign.length, 2); 17 18 // Basic functionality works with multiple sources 19 function basicMultipleSources() { 20 var a = {}; 21 var b = { bProp : 7 }; 22 var c = { cProp : 8 }; 23 Object.assign(a, b, c); 24 assertEq(a.bProp, 7); 25 assertEq(a.cProp, 8); 26 } 27 basicMultipleSources(); 28 29 // Basic functionality works with symbols (Bug 1052358) 30 function basicSymbols() { 31 var a = {}; 32 var b = { bProp : 7 }; 33 var aSymbol = Symbol("aSymbol"); 34 b[aSymbol] = 22; 35 Object.assign(a, b); 36 assertEq(a.bProp, 7); 37 assertEq(a[aSymbol], 22); 38 } 39 basicSymbols(); 40 41 // Calls ToObject() for target, skips null/undefined sources, uses 42 // ToObject(source) otherwise. 43 function testToObject() { 44 assertThrowsInstanceOf(() => Object.assign(null, null), TypeError); 45 assertThrowsInstanceOf(() => Object.assign(), TypeError); 46 assertThrowsInstanceOf(() => Object.assign(null, {}), TypeError); 47 assertEq(Object.assign({}, null) instanceof Object, true); 48 assertEq(Object.assign({}, undefined) instanceof Object, true); 49 50 // Technically an embedding could have this as extension acting differently 51 // from ours, so a feature-test is inadequate. We can move this subtest 52 // into extensions/ if that ever matters. 53 if (typeof createIsHTMLDDA === "function") { 54 var falsyObject = createIsHTMLDDA(); 55 falsyObject.foo = 7; 56 57 var obj = Object.assign({}, falsyObject); 58 assertEq(obj instanceof Object, true); 59 assertEq(obj.foo, 7); 60 } 61 62 assertEq(Object.assign(true, {}) instanceof Boolean, true); 63 assertEq(Object.assign(1, {}) instanceof Number, true); 64 assertEq(Object.assign("string", {}) instanceof String, true); 65 var o = {}; 66 assertEq(Object.assign(o, {}), o); 67 } 68 testToObject(); 69 70 // Invokes [[OwnPropertyKeys]] on ToObject(source) 71 function testOwnPropertyKeys() { 72 assertThrowsInstanceOf(() => Object.assign(null, new Proxy({}, { 73 getOwnPropertyNames: () => { throw new Error("not called"); } 74 })), TypeError); 75 76 var ownKeysCalled = false; 77 Object.assign({}, new Proxy({}, { 78 ownKeys: function() { 79 ownKeysCalled = true; 80 return []; 81 } 82 })); 83 assertEq(ownKeysCalled, true); 84 }; 85 testOwnPropertyKeys(); 86 87 // Ensure correct property traversal 88 function correctPropertyTraversal() { 89 var log = ""; 90 var source = new Proxy({a: 1, b: 2}, { 91 ownKeys: () => ["b", "c", "a"], 92 getOwnPropertyDescriptor: function(t, pk) { 93 log += "#" + pk; 94 return Object.getOwnPropertyDescriptor(t, pk); 95 }, 96 get: function(t, pk, r) { 97 log += "-" + pk; 98 return t[pk]; 99 }, 100 }); 101 Object.assign({}, source); 102 assertEq(log, "#b-b#c#a-a"); 103 } 104 correctPropertyTraversal(); 105 106 // Only [[Enumerable]] properties are assigned to target 107 function onlyEnumerablePropertiesAssigned() { 108 var source = Object.defineProperties({}, { 109 a: {value: 1, enumerable: true}, 110 b: {value: 2, enumerable: false}, 111 }); 112 var target = Object.assign({}, source); 113 assertEq("a" in target, true); 114 assertEq("b" in target, false); 115 } 116 onlyEnumerablePropertiesAssigned(); 117 118 119 // Enumerability is decided on-time, not before main loop (1) 120 function testEnumerabilityDeterminedInLoop1() 121 { 122 var getterCalled = false; 123 var sourceTarget = { 124 get a() { getterCalled = true }, 125 get b() { Object.defineProperty(sourceTarget, "a", {enumerable: false}) }, 126 }; 127 var source = new Proxy(sourceTarget, { ownKeys: () => ["b", "a"] }); 128 Object.assign({}, source); 129 assertEq(getterCalled, false); 130 } 131 testEnumerabilityDeterminedInLoop1(); 132 133 // Enumerability is decided on-time, not before main loop (2) 134 function testEnumerabilityDeterminedInLoop2() 135 { 136 var getterCalled = false; 137 var sourceTarget = { 138 get a() { getterCalled = true }, 139 get b() { Object.defineProperty(sourceTarget, "a", {enumerable: true}) }, 140 }; 141 var source = new Proxy(sourceTarget, { 142 ownKeys: () => ["b", "a"] 143 }); 144 Object.defineProperty(sourceTarget, "a", {enumerable: false}); 145 Object.assign({}, source); 146 assertEq(getterCalled, true); 147 } 148 testEnumerabilityDeterminedInLoop2(); 149 150 // Properties are retrieved through Get() and assigned onto 151 // the target as data properties, not in any sense cloned over as descriptors 152 function testPropertiesRetrievedThroughGet() { 153 var getterCalled = false; 154 Object.assign({}, {get a() { getterCalled = true }}); 155 assertEq(getterCalled, true); 156 } 157 testPropertiesRetrievedThroughGet(); 158 159 // Properties are retrieved through Get() 160 // Properties are assigned through Put() 161 function testPropertiesAssignedThroughPut() { 162 var setterCalled = false; 163 Object.assign({set a(v) { setterCalled = v }}, {a: true}); 164 assertEq(setterCalled, true); 165 } 166 testPropertiesAssignedThroughPut(); 167 168 // Properties are retrieved through Get() 169 // Properties are assigned through Put(): Existing property attributes are not altered 170 function propertiesAssignedExistingNotAltered() { 171 var source = {a: 1, b: 2, c: 3}; 172 var target = {a: 0, b: 0, c: 0}; 173 Object.defineProperty(target, "a", {enumerable: false}); 174 Object.defineProperty(target, "b", {configurable: false}); 175 Object.defineProperty(target, "c", {enumerable: false, configurable: false}); 176 Object.assign(target, source); 177 checkDataProperty(target, "a", 1, true, false, true); 178 checkDataProperty(target, "b", 2, true, true, false); 179 checkDataProperty(target, "c", 3, true, false, false); 180 } 181 propertiesAssignedExistingNotAltered(); 182 183 // Properties are retrieved through Get() 184 // Properties are assigned through Put(): Throws TypeError if non-writable 185 function propertiesAssignedTypeErrorNonWritable() { 186 var source = {a: 1}; 187 var target = {a: 0}; 188 Object.defineProperty(target, "a", {writable: false}); 189 assertThrowsInstanceOf(() => Object.assign(target, source), TypeError); 190 checkDataProperty(target, "a", 0, false, true, true); 191 } 192 propertiesAssignedTypeErrorNonWritable(); 193 194 // Properties are retrieved through Get() 195 // Put() creates standard properties; Property attributes from source are ignored 196 function createsStandardProperties() { 197 var source = {a: 1, b: 2, c: 3, get d() { return 4 }}; 198 Object.defineProperty(source, "b", {writable: false}); 199 Object.defineProperty(source, "c", {configurable: false}); 200 var target = Object.assign({}, source); 201 checkDataProperty(target, "a", 1, true, true, true); 202 checkDataProperty(target, "b", 2, true, true, true); 203 checkDataProperty(target, "c", 3, true, true, true); 204 checkDataProperty(target, "d", 4, true, true, true); 205 } 206 createsStandardProperties(); 207 208 // Properties created during traversal are not copied 209 function propertiesCreatedDuringTraversalNotCopied() { 210 var source = {get a() { this.b = 2 }}; 211 var target = Object.assign({}, source); 212 assertEq("a" in target, true); 213 assertEq("b" in target, false); 214 } 215 propertiesCreatedDuringTraversalNotCopied(); 216 217 // Properties deleted during traversal are not copied 218 function testDeletePropertiesNotCopied() { 219 var source = new Proxy({ 220 get a() { delete this.b }, 221 b: 2, 222 }, { 223 getOwnPropertyNames: () => ["a", "b"] 224 }); 225 var target = Object.assign({}, source); 226 assertEq("a" in target, true); 227 assertEq("b" in target, false); 228 } 229 testDeletePropertiesNotCopied(); 230 231 function testDeletionExposingShadowedProperty() 232 { 233 var srcProto = { b: 42 }; 234 var src = 235 Object.create(srcProto, 236 { a: { enumerable: true, get: function() { delete this.b; } }, 237 b: { value: 2, configurable: true, enumerable: true } }); 238 var source = new Proxy(src, { getOwnPropertyNames: () => ["a", "b"] }); 239 var target = Object.assign({}, source); 240 assertEq("a" in target, true); 241 assertEq("b" in target, false); 242 } 243 testDeletionExposingShadowedProperty(); 244 245 // Properties first deleted and then recreated during traversal are copied (1) 246 function testDeletedAndRecreatedPropertiesCopied1() { 247 var source = new Proxy({ 248 get a() { delete this.c }, 249 get b() { this.c = 4 }, 250 c: 3, 251 }, { 252 getOwnPropertyNames: () => ["a", "b", "c"] 253 }); 254 var target = Object.assign({}, source); 255 assertEq("a" in target, true); 256 assertEq("b" in target, true); 257 assertEq("c" in target, true); 258 checkDataProperty(target, "c", 4, true, true, true); 259 } 260 testDeletedAndRecreatedPropertiesCopied1(); 261 262 // Properties first deleted and then recreated during traversal are copied (2) 263 function testDeletedAndRecreatedPropertiesCopied2() { 264 var source = new Proxy({ 265 get a() { delete this.c }, 266 get b() { this.c = 4 }, 267 c: 3, 268 }, { 269 ownKeys: () => ["a", "c", "b"] 270 }); 271 var target = Object.assign({}, source); 272 assertEq("a" in target, true); 273 assertEq("b" in target, true); 274 assertEq("c" in target, false); 275 } 276 testDeletedAndRecreatedPropertiesCopied2(); 277 278 // String and Symbol valued properties are copied 279 function testStringAndSymbolPropertiesCopied() { 280 var keyA = "str-prop"; 281 var source = {"str-prop": 1}; 282 var target = Object.assign({}, source); 283 checkDataProperty(target, keyA, 1, true, true, true); 284 } 285 testStringAndSymbolPropertiesCopied(); 286 287 // Intermediate exceptions stop traversal and throw exception 288 function testExceptionsDoNotStopFirstReported1() { 289 var TestError = function TestError() {}; 290 var source = new Proxy({}, { 291 getOwnPropertyDescriptor: function(t, pk) { 292 assertEq(pk, "b"); 293 throw new TestError(); 294 }, 295 ownKeys: () => ["b", "a"] 296 }); 297 assertThrowsInstanceOf(() => Object.assign({}, source), TestError); 298 } 299 testExceptionsDoNotStopFirstReported1(); 300 301 302 if (typeof reportCompare == "function") 303 reportCompare(true, true);