| /* Any copyright is dedicated to the Public Domain. |
| * http://creativecommons.org/licenses/publicdomain/ */ |
| |
| // Reflect.set does property assignment. |
| // With three arguments, this is pretty straightforward. |
| var obj = {}; |
| assertEq(Reflect.set(obj, "prop", "value"), true); |
| assertEq(obj.prop, "value"); |
| |
| |
| // === Various targets |
| |
| // It can assign array elements. |
| var arr = ["duck", "duck", "duck"]; |
| assertEq(Reflect.set(arr, 2, "goose"), true); |
| assertEq(arr[2], "goose"); |
| |
| // It can extend an array. |
| assertEq(Reflect.set(arr, 3, "Model T"), true); |
| assertEq(arr.length, 4); |
| |
| // It can truncate an array. |
| assertEq(Reflect.set(arr, "length", 1), true); |
| assertDeepEq(arr, ["duck"]); |
| |
| // It won't assign to non-writable properties of String objects. |
| var str = new String("hello"); |
| assertEq(Reflect.set(str, "0", "y"), false); |
| assertEq(str[0], "h"); |
| assertEq(Reflect.set(str, "length", 700), false); |
| assertEq(str.length, 5); |
| |
| |
| // === Receivers |
| // The optional fourth argument is the receiver, which [[Set]] methods use for |
| // various things. |
| |
| // On ordinary objects, if the property has a setter, the receiver is passed as |
| // the this-value to the setter. |
| var expected; |
| var obj = { |
| set prop(v) { |
| "use strict"; |
| assertEq(v, 32); |
| assertEq(this, expected); |
| } |
| }; |
| for (expected of [obj, {}, [], 37.3]) { |
| assertEq(Reflect.set(obj, "prop", 32, expected), true); |
| } |
| |
| // If the property doesn't already exist, it is defined on the receiver. |
| obj = {}; |
| var obj2 = {}; |
| assertEq(Reflect.set(obj, "prop", 47, obj2), true); |
| assertDeepEq(obj, {}); |
| assertDeepEq(Reflect.getOwnPropertyDescriptor(obj2, "prop"), |
| {value: 47, writable: true, enumerable: true, configurable: true}); |
| |
| // If the property doesn't already exist, and the receiver isn't an object, return false. |
| for (var v of SOME_PRIMITIVE_VALUES) { |
| assertEq(Reflect.set({}, "x", 0, v), false); |
| } |
| |
| // Receiver defaults to the target. |
| obj = {}; |
| var hits; |
| var expectedReceiver; |
| var proxy = new Proxy(obj, { |
| set(t, k, v, r) { |
| assertEq(t, obj); |
| assertEq(k, "key"); |
| assertEq(v, "value"); |
| assertEq(r, expectedReceiver); // not obj |
| hits++; |
| return true; |
| } |
| }); |
| hits = 0; |
| expectedReceiver = proxy; |
| assertEq(Reflect.set(proxy, "key", "value"), true); |
| assertEq(hits, 1); |
| |
| // But not if explicitly present and undefined. |
| hits = 0; |
| expectedReceiver = undefined; |
| assertEq(Reflect.set(proxy, "key", "value", undefined), true); |
| assertEq(hits, 1); |
| |
| // Reflect.set can be used as fallback behavior in a proxy handler .set() |
| // method. |
| var log; |
| obj = { |
| set prop(v) { |
| log += "p"; |
| assertEq(v, "value"); |
| assertEq(this, proxy); // not obj! |
| } |
| }; |
| proxy = new Proxy(obj, { |
| set(t, k, v, r) { |
| assertEq(t, obj); |
| assertEq(r, proxy); |
| log += "s"; |
| return Reflect.set(t, k, v, r); |
| } |
| }); |
| log = ""; |
| assertEq(Reflect.set(proxy, "prop", "value"), true); |
| assertEq(log, "sp"); |
| |
| |
| // === Cross-compartment wrapper behavior. |
| |
| // When calling a cross-compartment wrapper, receiver is rewrapped for the |
| // target compartment. |
| var g = newGlobal(); |
| if (!("assertEq" in g)) |
| g.assertEq = assertEq; // necessary in the browser |
| g.eval(` |
| var hits; |
| var obj = { |
| set x(v) { |
| "use strict"; |
| assertEq(this, receiver); |
| assertEq(v, "xyzzy"); |
| hits++; |
| } |
| }; |
| var receiver = {}; |
| `); |
| g.hits = 0; |
| assertEq(Reflect.set(g.obj, "x", "xyzzy", g.receiver), true); |
| assertEq(g.hits, 1); |
| |
| // ...even when receiver is from a different compartment than target. |
| var receiver = {}; |
| g.receiver = receiver; |
| g.hits = 0; |
| assertEq(Reflect.set(g.obj, "x", "xyzzy", receiver), true); |
| assertEq(g.hits, 1); |
| |
| // ...even when receiver is a primtive value, even undefined. |
| for (receiver of SOME_PRIMITIVE_VALUES) { |
| g.receiver = receiver; |
| g.hits = 0; |
| assertEq(Reflect.set(g.obj, "x", "xyzzy", receiver), true); |
| assertEq(g.hits, 1); |
| } |
| |
| |
| // === Less than 3 arguments |
| |
| // With two arguments, the value is assumed to be undefined. |
| obj = {}; |
| assertEq(Reflect.set(obj, "size"), true); |
| assertDeepEq(Reflect.getOwnPropertyDescriptor(obj, "size"), |
| {value: undefined, writable: true, enumerable: true, configurable: true}); |
| |
| // With just one argument, the key is "undefined". |
| obj = {}; |
| assertEq(Reflect.set(obj), true); |
| assertDeepEq(Reflect.getOwnPropertyDescriptor(obj, "undefined"), |
| {value: undefined, writable: true, enumerable: true, configurable: true}); |
| |
| // For the no argument-case, see target.js. |
| |
| |
| // === Failure cases |
| |
| // Non-writable data property |
| obj = {}; |
| Reflect.defineProperty(obj, "x", {value: 0, writable: false}); |
| assertEq(Reflect.set(obj, "x", 1), false); |
| assertEq(obj.x, 0); |
| |
| // The same, but inherited from a prototype |
| var obj2 = Object.create(obj); |
| assertEq(Reflect.set(obj2, "x", 1), false); |
| assertEq(obj2.hasOwnProperty("x"), false); |
| assertEq(obj2.x, 0); |
| |
| // Getter, no setter |
| obj = {}; |
| var desc = {get: () => 12, set: undefined, enumerable: false, configurable: true}; |
| Reflect.defineProperty(obj, "y", desc); |
| assertEq(Reflect.set(obj, "y", 13), false); |
| assertDeepEq(Reflect.getOwnPropertyDescriptor(obj, "y"), desc); |
| |
| // The same, but inherited from a prototype |
| obj2 = Object.create(obj); |
| assertEq(Reflect.set(obj2, "y", 1), false); |
| assertEq(obj2.hasOwnProperty("y"), false); |
| assertDeepEq(Reflect.getOwnPropertyDescriptor(obj, "y"), desc); |
| |
| // Proxy set handler returns a false value |
| for (var no of [false, ""]) { |
| var hits = 0; |
| obj = {}; |
| var proxy = new Proxy(obj, { |
| set(t, k, v, r) { |
| assertEq(t, obj); |
| assertEq(k, "x"); |
| assertEq(v, 33); |
| assertEq(r, proxy); |
| hits++; |
| return no; |
| } |
| }); |
| assertEq(Reflect.set(proxy, "x", 33), false); |
| assertEq(hits, 1); |
| assertEq("x" in obj, false); |
| } |
| |
| // Proxy handler method throws |
| obj = {}; |
| proxy = new Proxy(obj, { |
| set(t, k, v, r) { throw "i don't like " + v; } |
| }); |
| assertThrowsValue(() => Reflect.set(proxy, "food", "cheese"), "i don't like cheese"); |
| |
| // If a Proxy set handler breaks the object invariants, it's a TypeError. |
| for (obj of [{a: 0}, {get a() { return 0; }}]) { |
| Object.freeze(obj); |
| proxy = new Proxy(obj, { |
| set(t, k, v, r) { return true; } |
| }); |
| assertThrowsInstanceOf(() => Reflect.set(proxy, "a", "b"), TypeError); |
| } |
| |
| // Per spec, this should first call p.[[Set]]("0", 42, a) and |
| // then (since p has no own properties) a.[[Set]]("0", 42, a). |
| // The latter should not define a property on p. |
| var a = [0, 1, 2, 3]; |
| var p = Object.create(a); |
| Reflect.set(p, "0", 42, a); |
| assertEq(p.hasOwnProperty("0"), false); |
| assertDeepEq(Reflect.getOwnPropertyDescriptor(a, "0"), |
| {value: 42, writable: true, enumerable: true, configurable: true}); |
| |
| // Test behavior of ordinary objects' [[Set]] method (ES6 9.1.9). |
| // On an ordinary object, if the property key isn't present, [[Set]] calls |
| // receiver.[[GetOwnProperty]]() and then receiver.[[DefineProperty]](). |
| var log; |
| obj = {}; |
| var proxyTarget = {}; |
| var existingDescriptor, expected, defineResult; |
| var receiver = new Proxy(proxyTarget, { |
| getOwnPropertyDescriptor(t, k) { |
| log += "g"; |
| return existingDescriptor; |
| }, |
| defineProperty(t, k, desc) { |
| log += "d"; |
| assertEq(t, proxyTarget); |
| assertEq(k, "prop"); |
| assertDeepEq(desc, expected); |
| return defineResult; |
| } |
| }); |
| existingDescriptor = undefined; |
| expected = {value: 5, writable: true, enumerable: true, configurable: true}; |
| for (var defineResult of [true, false]) { |
| log = ""; |
| assertEq(Reflect.set(obj, "prop", 5, receiver), defineResult); |
| assertEq(log, "gd"); |
| } |
| |
| existingDescriptor = {value: 7, writable: true, enumerable: false, configurable: true}; |
| expected = {value: 4}; |
| for (var defineResult of [true, false]) { |
| log = ""; |
| assertEq(Reflect.set(obj, "prop", 4, receiver), defineResult); |
| assertEq(log, "gd"); |
| } |
| |
| |
| // For more Reflect.set tests, see target.js and propertyKeys.js. |
| |
| reportCompare(0, 0); |