/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/licenses/publicdomain/ */ // Reflect.defineProperty defines properties. var obj = {}; assertEq(Reflect.defineProperty(obj, "x", {value: 7}), true); assertEq(obj.x, 7); var desc = Reflect.getOwnPropertyDescriptor(obj, "x"); assertDeepEq(desc, {value: 7, writable: false, enumerable: false, configurable: false}); // Reflect.defineProperty can define a symbol-keyed property. var key = Symbol(":o)"); assertEq(Reflect.defineProperty(obj, key, {value: 8}), true); assertEq(obj[key], 8); // array .length property obj = [1, 2, 3, 4, 5]; assertEq(Reflect.defineProperty(obj, "length", {value: 4}), true); assertDeepEq(obj, [1, 2, 3, 4]); // The target can be a proxy. obj = {}; var proxy = new Proxy(obj, { defineProperty(t, id, desc) { t[id] = 1; return true; } }); assertEq(Reflect.defineProperty(proxy, "prop", {value: 7}), true); assertEq(obj.prop, 1); assertEq(delete obj.prop, true); assertEq("prop" in obj, false); // The attributes object is re-parsed, not passed through to the // handler.defineProperty method. obj = {}; var attributes = { configurable: 17, enumerable: undefined, value: null }; proxy = new Proxy(obj, { defineProperty(t, id, desc) { assertEq(desc !== attributes, true); assertEq(desc.configurable, true); assertEq(desc.enumerable, false); assertEq(desc.value, null); assertEq("writable" in desc, false); return 15; // and the return value here is coerced to boolean } }); assertEq(Reflect.defineProperty(proxy, "prop", attributes), true); // === Failure and error cases // // Reflect.defineProperty behaves much like Object.defineProperty, which has // extremely thorough tests elsewhere, and the implementation is largely // shared. Duplicating those tests with Reflect.defineProperty would be a // big waste. // // However, certain failures cause Reflect.defineProperty to return false // without throwing a TypeError (unlike Object.defineProperty). So here we test // many error cases to check that behavior. // missing attributes argument assertThrowsInstanceOf(() => Reflect.defineProperty(obj, "y"), TypeError); // non-object attributes argument for (var attributes of SOME_PRIMITIVE_VALUES) { assertThrowsInstanceOf(() => Reflect.defineProperty(obj, "y", attributes), TypeError); } // inextensible object obj = Object.preventExtensions({}); assertEq(Reflect.defineProperty(obj, "prop", {value: 4}), false); // inextensible object with irrelevant inherited property obj = Object.preventExtensions(Object.create({"prop": 3})); assertEq(Reflect.defineProperty(obj, "prop", {value: 4}), false); // redefine nonconfigurable to configurable obj = Object.freeze({prop: 1}); assertEq(Reflect.defineProperty(obj, "prop", {configurable: true}), false); // redefine enumerability of nonconfigurable property obj = Object.freeze(Object.defineProperties({}, { x: {enumerable: true, configurable: false, value: 0}, y: {enumerable: false, configurable: false, value: 0}, })); assertEq(Reflect.defineProperty(obj, "x", {enumerable: false}), false); assertEq(Reflect.defineProperty(obj, "y", {enumerable: true}), false); // redefine nonconfigurable data to accessor property, or vice versa obj = Object.seal({x: 1, get y() { return 2; }}); assertEq(Reflect.defineProperty(obj, "x", {get() { return 2; }}), false); assertEq(Reflect.defineProperty(obj, "y", {value: 1}), false); // redefine nonwritable, nonconfigurable property as writable obj = Object.freeze({prop: 0}); assertEq(Reflect.defineProperty(obj, "prop", {writable: true}), false); assertEq(Reflect.defineProperty(obj, "prop", {writable: false}), true); // no-op // change value of nonconfigurable nonwritable property obj = Object.freeze({prop: 0}); assertEq(Reflect.defineProperty(obj, "prop", {value: -0}), false); assertEq(Reflect.defineProperty(obj, "prop", {value: +0}), true); // no-op // change getter or setter function g() {} function s(x) {} obj = {}; Object.defineProperty(obj, "prop", {get: g, set: s, configurable: false}); assertEq(Reflect.defineProperty(obj, "prop", {get: s}), false); assertEq(Reflect.defineProperty(obj, "prop", {get: g}), true); // no-op assertEq(Reflect.defineProperty(obj, "prop", {set: g}), false); assertEq(Reflect.defineProperty(obj, "prop", {set: s}), true); // no-op // Proxy defineProperty handler method that returns false var falseValues = [false, 0, -0, "", NaN, null, undefined]; if (typeof objectEmulatingUndefined === "function") falseValues.push(objectEmulatingUndefined()); var value; proxy = new Proxy({}, { defineProperty(t, id, desc) { return value; } }); for (value of falseValues) { assertEq(Reflect.defineProperty(proxy, "prop", {value: 1}), false); } // Proxy defineProperty handler method returns true, in violation of invariants. // Per spec, this is a TypeError, not a false return. obj = Object.freeze({x: 1}); proxy = new Proxy(obj, { defineProperty(t, id, desc) { return true; } }); assertThrowsInstanceOf(() => Reflect.defineProperty(proxy, "x", {value: 2}), TypeError); assertThrowsInstanceOf(() => Reflect.defineProperty(proxy, "y", {value: 0}), TypeError); assertEq(Reflect.defineProperty(proxy, "x", {value: 1}), true); // The second argument is converted ToPropertyKey before any internal methods // are called on the first argument. var poison = (counter => new Proxy({}, new Proxy({}, { get() { throw counter++; } })))(42); assertThrowsValue(() => { Reflect.defineProperty(poison, { toString() { throw 17; }, valueOf() { throw 8675309; } }, poison); }, 17); // For more Reflect.defineProperty tests, see target.js and propertyKeys.js. reportCompare(0, 0);