Index: test/mjsunit/harmony/proxies-json.js |
diff --git a/test/mjsunit/harmony/proxies-json.js b/test/mjsunit/harmony/proxies-json.js |
index 43cb09f7a826d6ed950a8c642d12992b251bf199..49129319919f8aef8a0f0f70121c5a29c9736cfe 100644 |
--- a/test/mjsunit/harmony/proxies-json.js |
+++ b/test/mjsunit/harmony/proxies-json.js |
@@ -25,7 +25,13 @@ |
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
-// Flags: --harmony-proxies |
+// Flags: --harmony-proxies --harmony-reflect |
+ |
+ |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+// JSON.stringify |
+ |
function testStringify(expected, object) { |
// Test fast case that bails out to slow case. |
@@ -34,23 +40,30 @@ function testStringify(expected, object) { |
assertEquals(expected, JSON.stringify(object, undefined, 0)); |
} |
-// Test serializing a proxy, function proxy and objects that contain them. |
+ |
+// Test serializing a proxy, a function proxy, and objects that contain them. |
+ |
var handler1 = { |
get: function(target, name) { |
return name.toUpperCase(); |
}, |
- enumerate: function(target) { |
+ ownKeys: function() { |
return ['a', 'b', 'c']; |
}, |
- getOwnPropertyDescriptor: function(target, name) { |
- return { enumerable: true }; |
+ getOwnPropertyDescriptor: function() { |
+ return { enumerable: true, configurable: true }; |
} |
} |
var proxy1 = new Proxy({}, handler1); |
testStringify('{"a":"A","b":"B","c":"C"}', proxy1); |
-var proxy_fun = Proxy.createFunction(handler1, function() { return 1; }); |
+var proxy_fun = new Proxy(() => {}, handler1); |
+assertTrue(typeof(proxy_fun) === 'function'); |
+testStringify(undefined, proxy_fun); |
+testStringify('[1,null]', [1, proxy_fun]); |
+ |
+handler1.apply = function() { return 666; }; |
testStringify(undefined, proxy_fun); |
testStringify('[1,null]', [1, proxy_fun]); |
@@ -63,17 +76,19 @@ testStringify('{"a":123,"b":{"a":"A","b":"B","c":"C"},"c":true}', parent1b); |
var parent1c = [123, proxy1, true]; |
testStringify('[123,{"a":"A","b":"B","c":"C"},true]', parent1c); |
+ |
// Proxy with side effect. |
+ |
var handler2 = { |
get: function(target, name) { |
delete parent2.c; |
return name.toUpperCase(); |
}, |
- enumerate: function(target) { |
+ ownKeys: function() { |
return ['a', 'b', 'c']; |
}, |
- getOwnPropertyDescriptor: function(target, name) { |
- return { enumerable: true }; |
+ getOwnPropertyDescriptor: function() { |
+ return { enumerable: true, configurable: true }; |
} |
} |
@@ -84,17 +99,21 @@ assertEquals(expected2, JSON.stringify(parent2)); |
parent2.c = "remove"; // Revert side effect. |
assertEquals(expected2, JSON.stringify(parent2, undefined, 0)); |
-// Proxy with a get function that uses the first argument. |
+ |
+// Proxy with a get function that uses the receiver argument. |
+ |
var handler3 = { |
- get: function(target, name) { |
- if (name == 'valueOf') return function() { return "proxy" }; |
- return name + "(" + target + ")"; |
+ get: function(target, name, receiver) { |
+ if (name == 'valueOf' || name === Symbol.toPrimitive) { |
+ return function() { return "proxy" }; |
+ }; |
+ if (typeof name !== 'symbol') return name + "(" + receiver + ")"; |
}, |
- enumerate: function(target) { |
+ ownKeys: function() { |
return ['a', 'b', 'c']; |
}, |
- getOwnPropertyDescriptor: function(target, name) { |
- return { enumerable: true }; |
+ getOwnPropertyDescriptor: function() { |
+ return { enumerable: true, configurable: true }; |
} |
} |
@@ -103,13 +122,18 @@ var parent3 = { x: 123, y: proxy3 } |
testStringify('{"x":123,"y":{"a":"a(proxy)","b":"b(proxy)","c":"c(proxy)"}}', |
parent3); |
+ |
// Empty proxy. |
+ |
var handler4 = { |
get: function(target, name) { |
return 0; |
}, |
enumerate: function(target) { |
- return []; |
+ return [][Symbol.iterator](); |
+ }, |
+ has: function() { |
+ return true; |
}, |
getOwnPropertyDescriptor: function(target, name) { |
return { enumerable: false }; |
@@ -120,14 +144,19 @@ var proxy4 = new Proxy({}, handler4); |
testStringify('{}', proxy4); |
testStringify('{"a":{}}', { a: proxy4 }); |
+ |
// Proxy that provides a toJSON function that uses this. |
+ |
var handler5 = { |
get: function(target, name) { |
if (name == 'z') return 97000; |
return function(key) { return key.charCodeAt(0) + this.z; }; |
}, |
enumerate: function(target) { |
- return ['toJSON', 'z']; |
+ return ['toJSON', 'z'][Symbol.iterator](); |
+ }, |
+ has: function() { |
+ return true; |
}, |
getOwnPropertyDescriptor: function(target, name) { |
return { enumerable: true }; |
@@ -137,13 +166,18 @@ var handler5 = { |
var proxy5 = new Proxy({}, handler5); |
testStringify('{"a":97097}', { a: proxy5 }); |
+ |
// Proxy that provides a toJSON function that returns undefined. |
+ |
var handler6 = { |
get: function(target, name) { |
return function(key) { return undefined; }; |
}, |
enumerate: function(target) { |
- return ['toJSON']; |
+ return ['toJSON'][Symbol.iterator](); |
+ }, |
+ has: function() { |
+ return true; |
}, |
getOwnPropertyDescriptor: function(target, name) { |
return { enumerable: true }; |
@@ -154,7 +188,9 @@ var proxy6 = new Proxy({}, handler6); |
testStringify('[1,null,true]', [1, proxy6, true]); |
testStringify('{"a":1,"c":true}', {a: 1, b: proxy6, c: true}); |
+ |
// Object containing a proxy that changes the parent's properties. |
+ |
var handler7 = { |
get: function(target, name) { |
delete parent7.a; |
@@ -162,11 +198,11 @@ var handler7 = { |
parent7.e = "5"; |
return name.toUpperCase(); |
}, |
- enumerate: function(target) { |
+ ownKeys: function() { |
return ['a', 'b', 'c']; |
}, |
- getOwnPropertyDescriptor: function(target, name) { |
- return { enumerable: true }; |
+ getOwnPropertyDescriptor: function() { |
+ return { enumerable: true, configurable: true }; |
} |
} |
@@ -176,3 +212,299 @@ assertEquals('{"a":"1","b":{"a":"A","b":"B","c":"C"},"d":"4"}', |
JSON.stringify(parent7)); |
assertEquals('{"b":{"a":"A","b":"B","c":"C"},"d":"4","e":"5"}', |
JSON.stringify(parent7)); |
+ |
+ |
+// (Proxy handler to log trap calls) |
+ |
+var log = []; |
+var logger = {}; |
+var handler = new Proxy({}, logger); |
+ |
+logger.get = function(t, trap, r) { |
+ return function() { |
+ log.push([trap, ...arguments]); |
+ return Reflect[trap](...arguments); |
+ } |
+}; |
+ |
+ |
+// Object is a callable proxy |
+ |
+log.length = 0; |
+var target = () => 42; |
+var proxy = new Proxy(target, handler); |
+assertTrue(typeof proxy === 'function'); |
+ |
+assertEquals(undefined, JSON.stringify(proxy)); |
+assertEquals(1, log.length) |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(["get", target, "toJSON", proxy], log[0]); |
+ |
+ |
+// Object is a non-callable non-arraylike proxy |
+ |
+log.length = 0; |
+var target = {foo: 42} |
+var proxy = new Proxy(target, handler); |
+assertFalse(Array.isArray(proxy)); |
+ |
+assertEquals('{"foo":42}', JSON.stringify(proxy)); |
+assertEquals(4, log.length) |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals( |
+ ["get", target, "toJSON", proxy], log[0]); |
+assertEquals( |
+ ["ownKeys", target], log[1]); // EnumerableOwnNames |
+assertEquals( |
+ ["getOwnPropertyDescriptor", target, "foo"], log[2]); // EnumerableOwnNames |
+assertEquals( |
+ ["get", target, "foo", proxy], log[3]); |
+ |
+ |
+// Object is an arraylike proxy |
+ |
+log.length = 0; |
+var target = [42]; |
+var proxy = new Proxy(target, handler); |
+assertTrue(Array.isArray(proxy)); |
+ |
+assertEquals('[42]', JSON.stringify(proxy)); |
+assertEquals(3, log.length) |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(["get", target, "toJSON", proxy], log[0]); |
+assertEquals(["get", target, "length", proxy], log[1]); |
+assertEquals(["get", target, "0", proxy], log[2]); |
+ |
+ |
+// Replacer is a callable proxy |
+ |
+log.length = 0; |
+var object = {0: "foo", 1: 666}; |
+var target = (key, val) => key == "1" ? val + 42 : val; |
+var proxy = new Proxy(target, handler); |
+assertTrue(typeof proxy === 'function'); |
+ |
+assertEquals('{"0":"foo","1":708}', JSON.stringify(object, proxy)); |
+assertEquals(3, log.length) |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(4, log[0].length) |
+assertEquals("apply", log[0][0]); |
+assertEquals("", log[0][3][0]); |
+assertEquals({0: "foo", 1: 666}, log[0][3][1]); |
+assertEquals(4, log[1].length) |
+assertEquals("apply", log[1][0]); |
+assertEquals(["0", "foo"], log[1][3]); |
+assertEquals(4, log[2].length) |
+assertEquals("apply", log[2][0]); |
+assertEquals(["1", 666], log[2][3]); |
+ |
+ |
+// Replacer is an arraylike proxy |
+ |
+log.length = 0; |
+var object = {0: "foo", 1: 666}; |
+var target = [0]; |
+var proxy = new Proxy(target, handler); |
+assertTrue(Array.isArray(proxy)); |
+ |
+assertEquals('{"0":"foo"}', JSON.stringify(object, proxy)); |
+assertEquals(2, log.length) |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(["get", target, "length", proxy], log[0]); |
+assertEquals(["get", target, "0", proxy], log[1]); |
+ |
+ |
+// Replacer is an arraylike proxy and object is an array |
+ |
+log.length = 0; |
+var object = ["foo", 42]; |
+var target = [0]; |
+var proxy = new Proxy(target, handler); |
+assertTrue(Array.isArray(proxy)); |
+ |
+assertEquals('["foo",42]', JSON.stringify(object, proxy)); |
+assertEquals(2, log.length); |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(["get", target, "length", proxy], log[0]); |
+assertEquals(["get", target, "0", proxy], log[1]); |
+ |
+ |
+// Replacer is an arraylike proxy with a non-trivial length |
+ |
+var getTrap = function(t, key) { |
+ if (key === "length") return {[Symbol.toPrimitive]() {return 42}}; |
+ if (key === "41") return "foo"; |
+ if (key === "42") return "bar"; |
+}; |
+var target = []; |
+var proxy = new Proxy(target, {get: getTrap}); |
+assertTrue(Array.isArray(proxy)); |
+var object = {foo: true, bar: 666}; |
+assertEquals('{"foo":true}', JSON.stringify(object, proxy)); |
+ |
+ |
+// Replacer is an arraylike proxy with a bogus length |
+ |
+var getTrap = function(t, key) { |
+ if (key === "length") return Symbol(); |
+ if (key === "41") return "foo"; |
+ if (key === "42") return "bar"; |
+}; |
+var target = []; |
+var proxy = new Proxy(target, {get: getTrap}); |
+assertTrue(Array.isArray(proxy)); |
+var object = {foo: true, bar: 666}; |
+assertThrows(() => JSON.stringify(object, proxy), TypeError); |
+ |
+ |
+// Replacer returns a non-callable non-arraylike proxy |
+ |
+log.length = 0; |
+var object = ["foo", 42]; |
+var target = {baz: 5}; |
+var proxy = new Proxy(target, handler); |
+var replacer = (key, val) => key === "1" ? proxy : val; |
+ |
+assertEquals('["foo",{"baz":5}]', JSON.stringify(object, replacer)); |
+assertEquals(3, log.length); |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(["ownKeys", target], log[0]); |
+assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]); |
+ |
+ |
+// Replacer returns an arraylike proxy |
+ |
+log.length = 0; |
+var object = ["foo", 42]; |
+var target = ["bar"]; |
+var proxy = new Proxy(target, handler); |
+var replacer = (key, val) => key === "1" ? proxy : val; |
+ |
+assertEquals('["foo",["bar"]]', JSON.stringify(object, replacer)); |
+assertEquals(2, log.length); |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(["get", target, "length", proxy], log[0]); |
+assertEquals(["get", target, "0", proxy], log[1]); |
+ |
+ |
+// Replacer returns an arraylike proxy with a non-trivial length |
+ |
+var getTrap = function(t, key) { |
+ if (key === "length") return {[Symbol.toPrimitive]() {return 3}}; |
+ if (key === "2") return "baz"; |
+ if (key === "3") return "bar"; |
+}; |
+var target = []; |
+var proxy = new Proxy(target, {get: getTrap}); |
+var replacer = (key, val) => key === "goo" ? proxy : val; |
+var object = {foo: true, goo: false}; |
+assertEquals('{"foo":true,"goo":[null,null,"baz"]}', |
+ JSON.stringify(object, replacer)); |
+ |
+ |
+// Replacer returns an arraylike proxy with a bogus length |
+ |
+var getTrap = function(t, key) { |
+ if (key === "length") return Symbol(); |
+ if (key === "2") return "baz"; |
+ if (key === "3") return "bar"; |
+}; |
+var target = []; |
+var proxy = new Proxy(target, {get: getTrap}); |
+var replacer = (key, val) => key === "goo" ? proxy : val; |
+var object = {foo: true, goo: false}; |
+assertThrows(() => JSON.stringify(object, replacer)); |
+ |
+ |
+// Replacer returns a callable proxy |
+ |
+log.length = 0; |
+var target = () => 666; |
+var proxy = new Proxy(target, handler); |
+var replacer = (key, val) => key === "1" ? proxy : val; |
+ |
+assertEquals('["foo",null]', JSON.stringify(["foo", 42], replacer)); |
+assertEquals(0, log.length); |
+ |
+assertEquals('{"0":"foo"}', JSON.stringify({0: "foo", 1: 42}, replacer)); |
+assertEquals(0, log.length); |
+ |
+ |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+// JSON.parse |
+ |
+ |
+// Reviver is a callable proxy |
+ |
+log.length = 0; |
+var target = () => 42; |
+var proxy = new Proxy(target, handler); |
+assertTrue(typeof proxy === "function"); |
+ |
+assertEquals(42, JSON.parse("[true, false]", proxy)); |
+assertEquals(3, log.length); |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(4, log[0].length); |
+assertEquals("apply", log[0][0]); |
+assertEquals(["0", true], log[0][3]); |
+assertEquals(4, log[1].length); |
+assertEquals("apply", log[1][0]); |
+assertEquals(["1", false], log[1][3]); |
+assertEquals(4, log[2].length); |
+assertEquals("apply", log[2][0]); |
+assertEquals(["", [42, 42]], log[2][3]); |
+ |
+ |
+// Reviver plants a non-arraylike proxy into a yet-to-be-visited property |
+ |
+log.length = 0; |
+var target = {baz: 42}; |
+var proxy = new Proxy(target, handler); |
+var reviver = function(p, v) { |
+ if (p === "baz") return 5; |
+ if (p === "foo") this.bar = proxy; |
+ return v; |
+} |
+ |
+assertEquals({foo: 0, bar: proxy}, JSON.parse('{"foo":0,"bar":1}', reviver)); |
+assertEquals(4, log.length); |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(["ownKeys", target], log[0]); |
+assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]); |
+assertEquals(["get", target, "baz", proxy], log[2]); |
+assertEquals(["defineProperty", target, "baz", |
+ {value: 5, configurable: true, writable: true, enumerable: true}], log[3]); |
+ |
+ |
+// Reviver plants an arraylike proxy into a yet-to-be-visited property |
+ |
+log.length = 0; |
+var target = [42]; |
+var proxy = new Proxy(target, handler); |
+assertTrue(Array.isArray(proxy)); |
+var reviver = function(p, v) { |
+ if (p === "0") return undefined; |
+ if (p === "foo") this.bar = proxy; |
+ return v; |
+} |
+ |
+var result = JSON.parse('{"foo":0,"bar":1}', reviver); |
+assertEquals({foo: 0, bar: proxy}, result); |
+assertSame(result.bar, proxy); |
+assertEquals(3, log.length); |
+for (var i in log) assertSame(target, log[i][1]); |
+ |
+assertEquals(["get", target, "length", proxy], log[0]); |
+assertEquals(["get", target, "0", proxy], log[1]); |
+assertEquals(["deleteProperty", target, "0"], log[2]); |