// FIXME: util function formatValue(v) { if (typeof v === 'function') { // avoid implementation dependent string formatting if (v.funcName) { return '[function ' + v.funcName + ']'; } else { return Object.prototype.toString.call(v); } } if (typeof v === 'number') { if (v === 0) { if (1/v === Number.NEGATIVE_INFINITY) { // format negative zero specially to detect them in the output return '-0'; } } } return String(v); } function getDesc(obj, prop) { var pd; if (typeof obj !== 'object' || obj === null) { return prop + ': non-object (' + Object.prototype.toString.call(obj) + ')'; } pd = Object.getOwnPropertyDescriptor(obj, prop); if (pd === undefined) { return prop + ': undefined'; } return prop + ': ' + 'value=' + formatValue(pd.value) + ', writable=' + formatValue(pd.writable) + ', enumerable=' + formatValue(pd.enumerable) + ', configurable=' + formatValue(pd.configurable) + ', typeof(get)=' + formatValue(pd.get) + ', typeof(set)=' + formatValue(pd.set); } function printDesc(obj, prop) { print(getDesc(obj, prop)); } /*=== coercion [object Undefined] [object Undefined] TypeError [object Undefined] [object Object] TypeError [object Null] [object Object] TypeError [object Boolean] [object Object] TypeError [object Boolean] [object Object] TypeError [object Number] [object Object] TypeError [object String] [object Object] TypeError [object Array] [object Object] ok [object Object] [object Object] ok [object Object] [object Undefined] TypeError [object Object] [object Undefined] TypeError [object Object] [object Null] TypeError [object Object] [object Boolean] ok [object Object] [object Boolean] ok [object Object] [object Number] ok [object Object] [object String] ok [object Object] [object Array] ok [object Object] [object Object] ok ===*/ /* Test coercion of 'O' and 'Properties' arguments. */ print('coercion'); function coercionTest() { function test(o, p, arg_count) { var t; try { if (arg_count === 0) { t = Object.defineProperties(); } else if (arg_count === 1) { t = Object.defineProperties(o); } else { t = Object.defineProperties(o, p); } print(Object.prototype.toString.call(o), Object.prototype.toString.call(p), 'ok'); } catch (e) { print(Object.prototype.toString.call(o), Object.prototype.toString.call(p), e.name); } } // coercion of 'O' test(undefined, undefined, 0); test(undefined, {}); test(null, {}); test(true, {}); test(false, {}); test(123, {}); test('quux', {}); test([1,2], {}); test({ foo: 1, bar: 2 }, {}); // coercion of 'P' test({}, undefined, 1); test({}, undefined); test({}, null); test({}, true); test({}, false); test({}, 123); test({}, ''); // <-- careful to avoid TypeError from ToPropertyDescriptor() test({}, []); // (use empty string, array, object) test({}, {}); // // coercion of 'O' and 'P'; 'O' is checked first -- how to test this? } try { coercionTest(); } catch (e) { print(e); } /*=== proplist prop1 prop2 proto-prop proto-prop: undefined nonenum-prop: undefined prop1: value=prop1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined prop2: value=prop2, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined prop1 prop2 ===*/ /* Property list is created from own, enumerable properties of 'Properties' * argument. * * This test expects enum order to match insertion order (but the testcase * is not marked custom). */ print('proplist'); function propListTest() { var proto = {}; var props; var obj; var i; props = Object.create(proto); // enumerable, but not 'own property' Object.defineProperty(proto, 'proto-prop', { value: { value: 'from-proto', writable: true, enumerable: true, configurable: true }, writable: true, enumerable: true, configurable: true }); // own property but not enumerable Object.defineProperty(props, 'nonenum-prop', { value: { value: 'nonenum', writable: true, enumerable: true, configurable: true }, writable: true, enumerable: false, configurable: true }); // own property, enumerable -> will appear Object.defineProperty(props, 'prop1', { value: { value: 'prop1', writable: true, enumerable: true, configurable: true }, writable: true, enumerable: true, configurable: true }); Object.defineProperty(props, 'prop2', { value: { value: 'prop2', writable: true, enumerable: true, configurable: true }, writable: false, enumerable: true, configurable: false }); // for-in will enumerate also inherited properties for (i in props) { print(i); } obj = {}; Object.defineProperties(obj, props); printDesc(obj, 'proto-prop'); printDesc(obj, 'nonenum-prop'); printDesc(obj, 'prop1'); printDesc(obj, 'prop2'); // prop1 and prop2 addition order should match order in 'props' for (i in obj) { print(i); } } try { propListTest(); } catch (e) { print(e); } /*=== topropdesc foo: value=2, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined TypeError foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined ===*/ /* All ToPropertyDescriptor() coercions and checks happen before any object * properties/attributes are changed. */ print('topropdesc'); function toPropDescTest() { var obj; // base case: works obj = { foo: 1 }; Object.defineProperties(obj, { foo: { value: 2 } }); printDesc(obj, 'foo'); // invalid desc -> nothing is changed obj = { foo: 1 }; try { Object.defineProperties(obj, { foo: { value: 3 }, // ok bar: { set: function(){}, value: 123 } // conflict }); } catch (e) { print(e.name); } printDesc(obj, 'foo'); } try { toPropDescTest(); } catch (e) { print(e); } /*=== multiple foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined bar: value=2, writable=false, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined foo bar TypeError foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined bar: value=immutable, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined foo ===*/ /* Multiple changes in one go. Note that since 'Properties' is an Object, * the same property cannot be edited twice in one defineProperties() call. * * Unlike the ToPropertyDescriptor() step, the first TypeError (or other * error) causes defineProperties() to bail out. */ print('multiple'); function multiplePropsTest() { var obj; var i; // base case obj = {}; Object.defineProperties(obj, { foo: { value: 1, writable: true, enumerable: true, configurable: true }, bar: { value: 2, writable: false, enumerable: true, configurable: false } }); printDesc(obj, 'foo'); printDesc(obj, 'bar'); for (i in obj) { // demonstrate enum order print(i); } // first edit bails out, so second one is not made obj = {}; Object.defineProperty(obj, 'foo', { value: 'configurable', writable: false, enumerable: false, configurable: true }); Object.defineProperty(obj, 'bar', { value: 'immutable', writable: false, enumerable: false, configurable: false }); try { Object.defineProperties(obj, { foo: { value: 1, writable: true, enumerable: true, configurable: true }, bar: { value: 2, writable: false, enumerable: true, configurable: false } }); } catch (e) { print(e.name); } printDesc(obj, 'foo'); printDesc(obj, 'bar'); for (i in obj) { // demonstrate enum order print(i); } } try { multiplePropsTest(); } catch (e) { print(e); } /*=== return true ===*/ /* Return value is 'O'. */ print('return'); function returnValueTest() { var obj = {}; var ret; ret = Object.defineProperties(obj, { foo: { value: 1, writable: true, enumerable: true, configurable: true } }); print(ret === obj); } try { returnValueTest(); } catch (e) { print(e); }