//------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- if (typeof(WScript) != "undefined") { WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); } var tests = { // Note: each test has name (string) and body (function) properties. // Success is when the body does not throw, failure -- when it throws. test01: { name: "formal arg: simple: verify connection: named vs indexed arg", body: function () { var passedValue = 1; function f(a) { var val1 = 2; a = val1; assert.areEqual(val1, a, "wrong value of named parameter (val1)"); assert.areEqual(val1, arguments[0], "wrong value of indexed parameter (val1)"); var val2 = 3; arguments[0] = val2; assert.areEqual(val2, arguments[0], "wrong value of indexed parameter (val2)"); assert.areEqual(val2, a, "wrong value of named parameter (val2)"); } f(passedValue); } }, test02: { name: "formal arg: defineProperty, check property descriptor", body: function () { var passedValue = 1; function f(a) { var val = 2; Object.defineProperty(arguments, 0, { configurable: false, enumerable: false, value: val }); // Note that we expect writable: true because this was omitted in defineProperty above // which is actually re-define property with all attributes == true. var expected = { configurable: false, enumerable: false, writable: true, value: val }; assert.areEqual(expected, Object.getOwnPropertyDescriptor(arguments, 0), "wrong value of getOwnPropertyDescriptor"); assert.areEqual(val, a, "wrong value of named parameter"); } f(passedValue); } }, test03: { name: "formal arg: defineProperty, set writable to false, verify writability and lost connection. WOOB 1128023", body: function () { var passedValue = 1; function f(a) { Object.defineProperty(arguments, 0, { writable: false }); var expected = { configurable: true, enumerable: true, writable: false, value: passedValue }; assert.areEqual(expected, Object.getOwnPropertyDescriptor(arguments, 0), "wrong value of getOwnPropertyDescriptor"); // Attempt to change arguments[0] which is not writable now. var val1 = 2; arguments[0] = val1; assert.areEqual(passedValue, arguments[0], "non-writable changed"); assert.areEqual(passedValue, a, "non-writable changed: named arg also changed"); // Change named arg value, verify we are in connection named vs indexed arg. var val2 = 3; a = val2; assert.areEqual(val2, a, "Attemp to change named arg: didn't work"); assert.areEqual(passedValue, arguments[0], "At this time we should not be connected, but we are"); } f(passedValue); } }, test04: { name: "formal arg: defineProperty, set writable to false AND set value, verify that value changed in both named and indexed arg and that the item was disconnected", body: function () { var passedValue = 1; function f(a) { var val1 = 2; var val2 = 3; Object.defineProperty(arguments, 0, { writable: false, value: val1 }); var expected = { configurable: true, enumerable: true, writable: false, value: val1 }; assert.areEqual(expected, Object.getOwnPropertyDescriptor(arguments, 0), "wrong value of getOwnPropertyDescriptor"); assert.areEqual(val1, arguments[0], "value: arguments[0]"); assert.areEqual(val1, a, "value: a"); // Verify we are disconnected now. a = val2; assert.areEqual(val2, a, "new value: a"); assert.areEqual(val1, arguments[0], "value: arguments[0] -- did not get disconnected!"); } f(passedValue); } }, test05: { name: "formal arg: defineProperty w/o cause of disconnect, verify still connected to named arg", body: function () { var passedValue = 1; var val1 = 2; var val2 = 3; function f(a) { Object.defineProperty(arguments, 0, { value: val1 }); a = val1; assert.areEqual(val1, arguments[0], "arguments[0] got disconnected"); arguments[0] = val2; assert.areEqual(val2, a, "a got disconnected"); } f(passedValue); } }, test06: { name: "formal arg: defineProperty, disconnect arg[0], verify that arg[1] is still connected", body: function () { function f(a, b) { Object.defineProperty(arguments, 0, { writable: false }); var val1 = 3; var val2 = 4; arguments[1] = val1; assert.areEqual(val1, b, "arg[1] got disconnected"); b = val2; assert.areEqual(val2, arguments[1], "arg[1] got disconnected"); } f(1, 2); } }, test07: { name: "formal arg: defineProperty: convert to accessor property", body: function () { function f(a) { var isGetterFired = false; var isSetterFired = false; Object.defineProperty(arguments, 0, { get: function() { isGetterFired = true; return this.value; }, set: function(arg) { isSetterFired = true; this.value = arg; } }); assert.areEqual(undefined, arguments[0], "unexpected arg[0] value right after conversion to accessor property"); assert.areEqual(true, isGetterFired, "isGetterFired (1)"); isGetterFired = false; var val1 = 2; arguments[0] = val1; assert.areEqual(true, isSetterFired, "isSetterFired"); assert.areEqual(val1, arguments[0], "get value after set"); assert.areEqual(true, isGetterFired, "isGetterFired (2)"); } f(1); } }, test08: { name: "formal arg: defineProperty: convert to accessor, then to data property, verify value and that connection is lost", body: function () { var passedValue = 1; var val1 = 2; var val2 = 3; function f(a) { Object.defineProperty(arguments, 0, { get: function() { return this.value; }, set: function(arg) { this.value = arg; } }); Object.defineProperty(arguments, 0, { value: val1 }); a = val2; assert.areEqual(arguments[0], val1, "arguments[0]"); assert.areNotEqual(arguments[0], a, "arguments[0] != a"); } f(passedValue); } }, test09: { name: "formal arg: defineProperty: force convert to ES5 version but keep connected, check enumeration", body: function () { var passedValue = 1; function f(a) { Object.defineProperty(arguments, 0, { enumerable: true }); var accumulator = ""; for (var i in arguments) { accumulator += i.toString() + ": " + arguments[i] + ";"; } assert.areEqual("0: " + passedValue + ";" , accumulator, "accumulator"); } f(passedValue); } }, test10: { name: "formal arg: defineProperty: set non-enumerable/non-writable/delete, check enumeration", body: function () { var passedValue1 = 2; var passedValue2 = 4; function f(a, b, c, d) { Object.defineProperty(arguments, 0, { enumerable: false }); // arguments[0].enumerable = false. Object.defineProperty(arguments, 1, { writable: false }); // arguments[1].writable = false -> disconnected. delete arguments[2]; // arguments[2] is deleted. var i, accumulator = ""; for (i in arguments) { accumulator += i.toString() + ": " + arguments[i] + ";"; } // Note that we expect [1].enumerable = true because this was omitted in defineProperty above // which is actually re-define property that previously already had enumerable = true. assert.areEqual("1: " + passedValue1 + ";" + "3: " + passedValue2 + ";", accumulator, "accumulator"); } f(1, passedValue1, 3, passedValue2); } }, test11: { name: "passed/undeclared arg: verify there is no correlation with Object.prototype indexed data properties. WOOB 1143896", body: function () { var passedValue = "passed"; Object.defineProperty(Object.prototype, 0, { value: "from proto", configurable: true, writable: false }); try { function f() { return arguments; } var argObj = f(passedValue); assert.areEqual(passedValue, argObj[0]); } finally { delete Object.prototype[0]; } } }, test12: { name: "formal arg: verify there is no correlation with Object.prototype indexed properties", body: function () { var passedValue = "passed"; Object.defineProperty(Object.prototype, 0, { value: "from proto", configurable: true, writable: false }); try { function f(a) { return arguments } var argObj = f(passedValue); assert.areEqual(passedValue, argObj[0]); } finally { delete Object.prototype[0]; } } }, test13: { name: "passed/undeclared arg: verify there is no correlation with Object.prototype indexed accessor properties. WOOB 1144602", body: function () { var initial = "initial"; var passedValue = "passed"; var data = initial; Object.defineProperty(Object.prototype, 0, { configurable: true, get: function() { return data; }, set: function(arg) { data = arg; } }); try { function f() { return arguments; } var argObj = f(passedValue); assert.areEqual(initial, data, "data: should not be changed as setter on prototype should not be fired"); assert.areEqual(passedValue, argObj[0], "argObj[0]"); } finally { delete Object.prototype[0]; } } }, test14: { name: "formal arg: verify there is no correlation with Object.prototype indexed accessor properties", body: function () { var initial = "initial"; var passedValue = "passed"; var data = initial; Object.defineProperty(Object.prototype, 0, { configurable: true, get: function() { return data; }, set: function(arg) { data = arg; } }); try { function f(a) { return arguments; } var argObj = f(passedValue); assert.areEqual(initial, data, "data: should not be changed as setter on prototype should not be fired"); assert.areEqual(passedValue, argObj[0], "argObj[0]"); } finally { delete Object.prototype[0]; } } }, test15: { name: "formal arg: delete, make sure it's deleted", body: function () { var passedValue = 1; function f(a) { Object.defineProperty(arguments, 0, { enumerable: false }); // Force convert to ES5 version. delete arguments[0]; assert.areEqual(undefined, arguments[0], "was not deleted."); assert.areEqual(passedValue, a, "a is changed."); } f(passedValue); } }, test16: { name: "formal arg: delete, add, check named arg is not changed", body: function () { var passedValue = 1; function f(a, b) { Object.defineProperty(arguments, 0, { enumerable: false }); // Force convert to ES5 version. delete arguments[0]; arguments[0] = passedValue + 1; assert.areEqual(passedValue, a, "a is changed."); } f(passedValue, 2); } }, test17: { name: "formal arg: delete, then defineProperty with attributes for data property, check the value", body: function () { var passedValue = 1; function f(a) { delete arguments[0]; var val = 2; Object.defineProperty(arguments, 0, { enumerable: true, configurable: true, writable: true, value: val }); assert.areEqual(val, arguments[0], "wrong value"); } f(passedValue); } }, test18: { name: "formal arg: delete, then defineProperty with attributes for accessor property, check the enumeration", body: function () { var passedValue = 1; var getter = function() {return this.value; }; var setter = function(arg) { this.value = arg; }; function f(a) { delete arguments[0]; Object.defineProperty(arguments, 0, { enumerable: true, configurable: true, get: getter, set: setter }); var expected = { configurable: true, enumerable: true, get: getter, set: setter }; assert.areEqual(expected, Object.getOwnPropertyDescriptor(arguments, 0), "wrong descriptor"); var accumulator = ""; for (var i in arguments) { accumulator += i.toString() + ": " + arguments[i] + ";"; } assert.areEqual("0: " + undefined + ";", accumulator, "accumulator 2"); } f(passedValue); } }, test19: { name: "formal arg, es5 heap arguments: delete, add, check enumerable/order", body: function () { var passedValue1 = 1; var passedValue2 = 2; var newValue1 = 100; var newValue2 = 200; var i, accumulator; function f(a, b) { // Scenario 1: delete prior to converting to ES5 version. delete arguments[0]; // Delete [0] prior to conversion to ES5. Object.defineProperty(arguments, 0, { configurable: true, enumerable: true, value: newValue1 }); // Bring back [0] by defineProperty. Now args is ES5. accumulator = ""; for (i in arguments) { accumulator += i.toString() + ": " + arguments[i] + ";"; } assert.areEqual("0: " + newValue1 + ";" + "1: " + passedValue2 + ";", accumulator, "accumulator 1"); // Scenario 2: delete after converting to ES5 version. Object.defineProperty(arguments, 0, { configurable: true, enumerable: true, writable: true, value: newValue1 }); // Bring back [0] by defineProperty. Now args is ES5. delete arguments[0]; // Delete [0] prior after conversion to ES5. arguments[0] = newValue2; // Bring back [0] by setting value. accumulator = ""; for (i in arguments) { accumulator += i.toString() + ": " + arguments[i] + ";"; } assert.areEqual("0: " + newValue2 + ";" + "1: " + passedValue2 + ";", accumulator, "accumulator 2"); } f(passedValue1, passedValue2); } }, test20: { name: "formal arg, es5 heap arguments: delete, add, keep another arg in objectArray and use one non-formal, check enumerable/order", body: function () { var passedValue1 = 1; var passedValue2 = 2; var passedValue3 = 3; var passedValue4 = 4; var newValue = 100; function f(a, b, c) { Object.defineProperty(arguments, 0, { enumerable: true }); // Add objectArray item Object.defineProperty(arguments, 2, { enumerable: true }); // Add objectArray item var accumulator = ""; delete arguments[0]; arguments[0] = newValue; for (var i in arguments) { accumulator += i.toString() + ": " + arguments[i] + ";"; } assert.areEqual( "0: " + newValue + ";" + "1: " + passedValue2 + ";" + "2: " + passedValue3 + ";" + "3: " + passedValue4 + ";", accumulator, "accumulator"); } f(passedValue1, passedValue2, passedValue3, passedValue4); } }, test21: { name: "formal arg: defineProperty, set enumerable to false, check getOwnPropertyNames", body: function (a, b) { function f(a) { Object.defineProperty(arguments, 0, { enumerable: false }); // Note: Object.getOwnPropertyNames returns all properties, even non-enumerable. var actual = Object.getOwnPropertyNames(arguments); var expected = { 0: "0", 1: "1", 2: "length", 3: "callee" }; assert.areEqual(expected, actual, "wrong property names"); } f(101, 102); } }, test22Helper: function test22Helper(isConvertNeeded, messagePrefix) { function mkerr(message) { return messagePrefix + ": " + message; } var passedValue = 1; var newPropertyName = "x"; function f(a, b) { if (isConvertNeeded) { Object.defineProperty(arguments, 1, { enumerable: true }); // Force convert to ES5 version. } Object.preventExtensions(arguments); // No new properties can be added. assert.areEqual(false, Object.isExtensible(arguments), mkerr("isExtensible")); try { Object.defineProperty(arguments, newPropertyName, { enumerable: true, value: 100 }); // add new property assert.fail(mkerr("did not throw exception")); } catch (ex) { } arguments[newPropertyName] = 100; assert.areEqual(undefined, arguments[newPropertyName], mkerr("New property was added after preventExtensions was called")); } f(passedValue, passedValue + 1); }, test22_1: { name: "arguments (non-ES5 version): call Object.preventExtensions, try add new property by defineProperty and direct set", body: function () { tests.test22Helper(false, "non-ES5 version"); } }, test22_2: { name: "arguments (ES5 version): call Object.preventExtensions, try add new property by defineProperty and direct set", body: function () { tests.test22Helper(true, "ES5 version"); } }, test23Helper: function test23Helper(isConvertNeeded, messagePrefix) { function mkerr(message) { return messagePrefix + ": " + message; } var passedValue = 1; function f(a, b) { if (isConvertNeeded) { Object.defineProperty(arguments, 1, { enumerable: true }); // Force convert to ES5 version. } Object.preventExtensions(arguments); // This causes configurable, writable = false for all properties + Object.preventExtensions. // Note: formals existed prior to calling Object.preventExtensions, thus they are still modifiable. assert.areEqual(false, Object.isExtensible(arguments), "isExtensible"); var actual = Object.getOwnPropertyDescriptor(arguments, 0); var expected = { configurable: true, enumerable: true, writable: true, value: passedValue }; assert.areEqual(expected, actual, mkerr("wrong descriptor - initial")); // Try to modify/re-configure // Note: do not change value here as it causes different code path than exercised by identified issue. Object.defineProperty(arguments, 0, { enumerable: false }); Object.defineProperty(arguments, 0, { writable: false }); Object.defineProperty(arguments, 0, { configurable: false }); var expected = { configurable: false, enumerable: false, writable: false, value: passedValue }; assert.areEqual(expected, Object.getOwnPropertyDescriptor(arguments, 0), mkerr("wrong descriptor - after redefine")); } f(passedValue, passedValue + 1); }, // After Object.preventExtensions(arguments) we can't modify the attributes on formals. test23_1: { name: "arguments (non-ES5 version): call Object.preventExtensions, make sure we can still modify atttibutes on formals without changing the value", body: function () { tests.test23Helper(false, "non-ES5 version"); } }, // After Object.preventExtensions(arguments) we can't modify the attributes on formals. test23_2: { name: "arguments (ES5 version): call Object.preventExtensions, make sure we can still modify atttibutes on formals without changing the value", body: function () { tests.test23Helper(true, "ES5 version"); } }, test24Helper: function test24Helper(isConvertNeeded, messagePrefix) { function mkerr(message) { return messagePrefix + ": " + message; } var passedValue = 1; function f(a, b) { if (isConvertNeeded) { Object.defineProperty(arguments, 1, { enumerable: true }); // Force convert to ES5 version. } Object.seal(arguments); // This causes configurable = false for all properties + Object.preventExtensions. assert.areEqual(true, Object.isSealed(arguments), mkerr("isSealed")); assert.areEqual(false, Object.isExtensible(arguments), mkerr("isExtensible")); var actual = Object.getOwnPropertyDescriptor(arguments, 0); var expected = { configurable: false, enumerable: true, writable: true, value: passedValue }; assert.areEqual(expected, actual, mkerr("wrong descriptor")); } f(passedValue, passedValue + 1); }, // Object.freeze(arguments -- not ES5 version) does not set configurable to false on formals. test24_1: { name: "arguments (non-ES5 version): call Object.seal, verify descriptor on formal", body: function () { tests.test24Helper(false, "non-ES5 version"); } }, test24_2: { name: "arguments (ES5 version): call Object.seal, verify descriptor on formal", body: function () { tests.test24Helper(true, "ES5 version"); } }, test25Helper: function test25Helper(isConvertNeeded, messagePrefix) { function mkerr(message) { return messagePrefix + ": " + message; } var passedValue = 1; function f(a, b) { if (isConvertNeeded) { Object.defineProperty(arguments, 1, { enumerable: true }); // Force convert to ES5 version. } Object.freeze(arguments); // This causes configurable AND writable = false for all properties + Object.preventExtensions. assert.areEqual(true, Object.isFrozen(arguments), mkerr("isFrozen")); assert.areEqual(true, Object.isSealed(arguments), mkerr("isSealed")); assert.areEqual(false, Object.isExtensible(arguments), mkerr("isExtensible")); var actual = Object.getOwnPropertyDescriptor(arguments, 0); var expected = { configurable: false, enumerable: true, writable: false, value: passedValue }; assert.areEqual(expected, actual, mkerr("wrong descriptor")); } f(passedValue, passedValue + 1); }, // Object.freeze(arguments -- not ES5 version) does not set configurable and writable to false on formals. test25_1: { name: "arguments (non-ES5 version): call Object.freeze, verify descriptor on formal", body: function () { tests.test25Helper(false, "non-ES5 version"); } }, test25_2: { name: "arguments (ES5 version): call Object.freeze, verify descriptor on formal", body: function () { tests.test25Helper(true, "ES5 version"); } }, test26: { name: "formal arg: delete, preventExtensions, enumerate, make sure the item is deleted", body: function () { var passedValue1 = 1; var passedValue2 = 2; function f(a, b) { delete arguments[1]; Object.preventExtensions(arguments); var accumulator = ""; for (var i in arguments) { accumulator += i.toString() + ": " + arguments[i] + ";"; } assert.areEqual("0: " + passedValue1 + ";", accumulator, "accumulator"); assert.areEqual(undefined, arguments[1], "arguments[1]"); } f(passedValue1, passedValue2); } }, test27: { name: "formal arg: convert to ES5 version, change value and set writable to false", body: function () { var passedValue1 = 1; var val = 2; function f(a) { Object.defineProperty(arguments, 0, { enumerable: true }); a = val; Object.defineProperty(arguments, 0, { writable: false }); var expected = { configurable: true, enumerable: true, writable: false, value: val }; assert.areEqual(expected, Object.getOwnPropertyDescriptor(arguments, 0)); } f(passedValue1); } }, test28: { name: "formal arg: convert to ES5 version, enumerate when number of actual params is less than number of formals", body: function () { var accumulator = ""; function f(a, b) { Object.preventExtensions(arguments); for (var i in arguments) { if (accumulator.length != 0) accumulator += ","; accumulator += arguments[i]; } } var value = 5; f(value); var expected = helpers.isVersion10OrLater ? value.toString() : value.toString() + ",undefined"; // IE9 compat mode -- Win8 558490. assert.areEqual(expected, accumulator, "Wrong accumulated value"); } }, } // tests. testRunner.runTests(tests);