| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| |
| // Functional WeakSet tests -- verifies the APIs work correctly |
| // Note however this does not verify the GC semantics of WeakSet |
| |
| WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); |
| |
| var tests = [ |
| { |
| name: "WeakSet constructor called on undefined or WeakSet.prototype returns new WeakSet object (and throws on null, non-extensible object)", |
| body: function () { |
| // WeakSet is no longer allowed to be called as a function unless the object it is given |
| // for its this argument already has the [[WeakSetData]] property on it. |
| // TODO: When we implement @@create support, update this test to reflect it. |
| // |
| assert.throws(function () { WeakSet.call(undefined); }, TypeError, "WeakSet.call() throws TypeError given undefined"); |
| assert.throws(function () { WeakSet.call(null); }, TypeError, "WeakSet.call() throws TypeError given null"); |
| assert.throws(function () { WeakSet.call(WeakSet.prototype); }, TypeError, "WeakSet.call() throws TypeError given WeakSet.prototype"); |
| /* |
| var weakset1 = WeakSet.call(undefined); |
| assert.isTrue(weakset1 !== null && weakset1 !== undefined && weakset1 !== WeakSet.prototype, "WeakSet constructor creates new WeakSet object when this is undefined"); |
| |
| var weakset2 = WeakSet.call(WeakSet.prototype); |
| assert.isTrue(weakset2 !== null && weakset2 !== undefined && weakset2 !== WeakSet.prototype, "WeakSet constructor creates new WeakSet object when this is equal to WeakSet.prototype"); |
| |
| var o = { }; |
| Object.preventExtensions(o); |
| |
| assert.throws(function () { WeakSet.call(null); }, TypeError, "WeakSet constructor throws on null"); |
| assert.throws(function () { WeakSet.call(o); }, TypeError, "WeakSet constructor throws on non-extensible object"); |
| */ |
| } |
| }, |
| |
| { |
| name: "WeakSet constructor throws when called on already initialized WeakSet object", |
| body: function () { |
| var weakset = new WeakSet(); |
| assert.throws(function () { WeakSet.call(weakset); }, TypeError); |
| |
| // WeakSet is no longer allowed to be called as a function unless the object it is given |
| // for its this argument already has the [[WeakSetData]] property on it. |
| // TODO: When we implement @@create support, update this test to reflect it. |
| /* |
| var obj = {}; |
| WeakSet.call(obj); |
| assert.throws(function () { WeakSet.call(obj); }, TypeError); |
| |
| function MyWeakSet() { |
| WeakSet.call(this); |
| } |
| MyWeakSet.prototype = new WeakSet(); |
| MyWeakSet.prototype.constructor = MyWeakSet; |
| |
| var myweakset = new MyWeakSet(); |
| assert.throws(function () { WeakSet.call(myweakset); }, TypeError); |
| assert.throws(function () { MyWeakSet.call(myweakset); }, TypeError); |
| */ |
| } |
| }, |
| |
| { |
| name: "WeakSet constructor populates the weakset with values from given optional iterable argument", |
| body: function () { |
| var keys = [ { }, { }, { }, { } ]; |
| var ws = new WeakSet([ keys[0], keys[1], keys[2] ]); |
| |
| assert.isTrue(ws.has(keys[0]), "ws has value keys[0]"); |
| assert.isTrue(ws.has(keys[1]), "ws has value keys[1]"); |
| assert.isTrue(ws.has(keys[2]), "ws has value keys[2]"); |
| |
| var customIterable = { |
| [Symbol.iterator]: function () { |
| var i = 0; |
| return { |
| next: function () { |
| return { |
| done: i > 3, |
| value: keys[i++] |
| }; |
| } |
| }; |
| } |
| }; |
| |
| ws = new WeakSet(customIterable); |
| |
| assert.isTrue(ws.has(keys[0]), "ws has key keys[0]"); |
| assert.isTrue(ws.has(keys[1]), "ws has key keys[1]"); |
| assert.isTrue(ws.has(keys[2]), "ws has key keys[2]"); |
| assert.isTrue(ws.has(keys[3]), "ws has key keys[3]"); |
| } |
| }, |
| |
| { |
| name: "WeakSet constructor throws exceptions for non- and malformed iterable arguments", |
| body: function () { |
| var iterableNoIteratorMethod = { [Symbol.iterator]: 123 }; |
| var iterableBadIteratorMethod = { [Symbol.iterator]: function () { } }; |
| var iterableNoIteratorNextMethod = { [Symbol.iterator]: function () { return { }; } }; |
| var iterableBadIteratorNextMethod = { [Symbol.iterator]: function () { return { next: 123 }; } }; |
| var iterableNoIteratorResultObject = { [Symbol.iterator]: function () { return { next: function () { } }; } }; |
| |
| assert.throws(function () { new WeakSet(123); }, TypeError, "new WeakSet() throws on non-object", "Function expected"); |
| assert.throws(function () { new WeakSet({ }); }, TypeError, "new WeakSet() throws on non-iterable object", "Function expected"); |
| assert.throws(function () { new WeakSet(iterableNoIteratorMethod); }, TypeError, "new WeakSet() throws on non-iterable object where @@iterator property is not a function", "Function expected"); |
| assert.throws(function () { new WeakSet(iterableBadIteratorMethod); }, TypeError, "new WeakSet() throws on non-iterable object where @@iterator function doesn't return an iterator", "Object expected"); |
| assert.throws(function () { new WeakSet(iterableNoIteratorNextMethod); }, TypeError, "new WeakSet() throws on iterable object where iterator object does not have next property", "Function expected"); |
| assert.throws(function () { new WeakSet(iterableBadIteratorNextMethod); }, TypeError, "new WeakSet() throws on iterable object where iterator object's next property is not a function", "Function expected"); |
| assert.throws(function () { new WeakSet(iterableNoIteratorResultObject); }, TypeError, "new WeakSet() throws on iterable object where iterator object's next method doesn't return an iterator result", "Object expected"); |
| } |
| }, |
| |
| { |
| name: "APIs throw TypeError where specified", |
| body: function () { |
| function MyWeakSetImposter() { } |
| MyWeakSetImposter.prototype = new WeakSet(); |
| MyWeakSetImposter.prototype.constructor = MyWeakSetImposter; |
| |
| var o = new MyWeakSetImposter(); |
| |
| assert.throws(function () { o.add(o); }, TypeError, "add should throw if this doesn't have WeakSetData property"); |
| assert.throws(function () { o.delete(o); }, TypeError, "delete should throw if this doesn't have WeakSetData property"); |
| assert.throws(function () { o.has(o); }, TypeError, "has should throw if this doesn't have WeakSetData property"); |
| |
| assert.throws(function () { WeakSet.prototype.add.call(); }, TypeError, "add should throw if called with no arguments"); |
| assert.throws(function () { WeakSet.prototype.delete.call(); }, TypeError, "delete should throw if called with no arguments"); |
| assert.throws(function () { WeakSet.prototype.has.call(); }, TypeError, "has should throw if called with no arguments"); |
| |
| assert.throws(function () { WeakSet.prototype.add.call(null, o); }, TypeError, "add should throw if this is null"); |
| assert.throws(function () { WeakSet.prototype.delete.call(null, o); }, TypeError, "delete should throw if this is null"); |
| assert.throws(function () { WeakSet.prototype.has.call(null, o); }, TypeError, "has should throw if this is null"); |
| |
| assert.throws(function () { WeakSet.prototype.add.call(undefined, o); }, TypeError, "add should throw if this is undefined"); |
| assert.throws(function () { WeakSet.prototype.delete.call(undefined, o); }, TypeError, "delete should throw if this is undefined"); |
| assert.throws(function () { WeakSet.prototype.has.call(undefined, o); }, TypeError, "has should throw if this is undefined"); |
| |
| var weakset = new WeakSet(); |
| |
| assert.throws(function () { weakset.add(null); }, TypeError, "add should throw if key is not an object, e.g. null"); |
| assert.throws(function () { weakset.add(undefined); }, TypeError, "add should throw if key is not an object, e.g. undefined"); |
| assert.throws(function () { weakset.add(true); }, TypeError, "add should throw if key is not an object, e.g. a boolean"); |
| assert.throws(function () { weakset.add(10); }, TypeError, "add should throw if key is not an object, e.g. a number"); |
| assert.throws(function () { weakset.add("hello"); }, TypeError, "add should throw if key is not an object, e.g. a string"); |
| } |
| }, |
| |
| { |
| name: "Non-object key argument silent fails delete and has", |
| body: function () { |
| var weakset = new WeakSet(); |
| |
| assert.isFalse(weakset.has(null), "null is not an object and cannot be a key in a WeakSet; has returns false"); |
| assert.isFalse(weakset.has(undefined), "undefined is not an object and cannot be a key in a WeakSet; has returns false"); |
| assert.isFalse(weakset.has(true), "boolean is not an object and cannot be a key in a WeakSet; has returns false"); |
| assert.isFalse(weakset.has(10), "number is not an object and cannot be a key in a WeakSet; has returns false"); |
| assert.isFalse(weakset.has("hello"), "string is not an object and cannot be a key in a WeakSet; has returns false"); |
| |
| assert.isFalse(weakset.delete(null), "null is not an object and cannot be a key in a WeakSet; delete returns false"); |
| assert.isFalse(weakset.delete(undefined), "undefined is not an object and cannot be a key in a WeakSet; delete returns false"); |
| assert.isFalse(weakset.delete(true), "boolean is not an object and cannot be a key in a WeakSet; delete returns false"); |
| assert.isFalse(weakset.delete(10), "number is not an object and cannot be a key in a WeakSet; delete returns false"); |
| assert.isFalse(weakset.delete("hello"), "string is not an object and cannot be a key in a WeakSet; delete returns false"); |
| |
| var booleanObject = new Boolean(true); |
| var numberObject = new Number(10); |
| var stringObject = new String("hello"); |
| |
| weakset.add(booleanObject); |
| weakset.add(numberObject); |
| weakset.add(stringObject); |
| |
| assert.isFalse(weakset.has(true), "boolean is not an object and cannot be a key in a WeakSet; has returns false"); |
| assert.isFalse(weakset.has(10), "number is not an object and cannot be a key in a WeakSet; has returns false"); |
| assert.isFalse(weakset.has("hello"), "string is not an object and cannot be a key in a WeakSet; has returns false"); |
| |
| assert.isFalse(weakset.delete(true), "boolean is not an object and cannot be a key in a WeakSet; delete returns false"); |
| assert.isFalse(weakset.delete(10), "number is not an object and cannot be a key in a WeakSet; delete returns false"); |
| assert.isFalse(weakset.delete("hello"), "string is not an object and cannot be a key in a WeakSet; delete returns false"); |
| } |
| }, |
| |
| { |
| name: "Basic usage, add, delete, has", |
| body: function () { |
| var weakset = new WeakSet(); |
| |
| var o = { }; |
| var p = { }; |
| var q = { }; |
| |
| weakset.add(o); |
| weakset.add(p); |
| weakset.add(q); |
| |
| assert.isTrue(weakset.has(o), "Should contain key o"); |
| assert.isTrue(weakset.has(p), "Should contain key p"); |
| assert.isTrue(weakset.has(q), "Should contain key q"); |
| assert.isFalse(weakset.has(weakset), "Should not contain other keys, 'weakset'"); |
| assert.isFalse(weakset.has({ }), "Should not contain other keys, '{ }'"); |
| |
| assert.isTrue(weakset.delete(p), "Should return true after deleting key p"); |
| |
| assert.isTrue(weakset.has(o), "Should still contain key o"); |
| assert.isFalse(weakset.has(p), "Should no longer contain key p"); |
| assert.isTrue(weakset.has(q), "Should still contain key q"); |
| |
| assert.isFalse(weakset.delete(p), "Should return false, p is no longer a key"); |
| |
| assert.isTrue(weakset.delete(o), "Should return true after deleting key o"); |
| assert.isTrue(weakset.delete(q), "Should return true after deleting key q"); |
| |
| assert.isFalse(weakset.has(o), "Should no longer contain key o"); |
| assert.isFalse(weakset.has(p), "Should still not contain key p"); |
| assert.isFalse(weakset.has(q), "Should no longer contain key q"); |
| } |
| }, |
| |
| { |
| name: "Not specifying arguments should default them to undefined", |
| body: function () { |
| var weakset = new WeakSet(); |
| |
| assert.throws(function () { weakset.add(); }, TypeError, "Should throw TypeError for implicit undefined; add"); |
| |
| assert.isFalse(weakset.has(), "Should return false for implicit undefined; has"); |
| assert.isFalse(weakset.delete(), "Should return false for implicit undefined; delete"); |
| } |
| }, |
| |
| { |
| name: "Extra arguments should be ignored", |
| body: function () { |
| var weakset = new WeakSet(); |
| var o = { }; |
| var p = { }; |
| var q = { }; |
| |
| assert.isFalse(weakset.has(o, p, q), "Looks for o, ignores p and q, weak weakset is empty and has should return false"); |
| assert.isFalse(weakset.delete(o, p, q), "Looks for o, ignores p and q, weak weakset is empty and delete should return false"); |
| |
| weakset.add(o, p, q); |
| |
| assert.isTrue(weakset.has(o), "Should contain o"); |
| assert.isFalse(weakset.has(p), "Should not contain p"); |
| assert.isFalse(weakset.has(q), "Should not contain q"); |
| assert.isTrue(weakset.has(o, p, q), "Ignores p and q, does have o"); |
| assert.isTrue(weakset.has(o, q, p), "Order of extra arguments has no affect, still has o"); |
| assert.isFalse(weakset.has(p, o), "Ignores o, does not have p"); |
| |
| assert.isFalse(weakset.delete(p, o, q), "p is not found so should return false, ignores o and q"); |
| assert.isFalse(weakset.delete(q, o), "q is not found so should return false, ignores o"); |
| assert.isTrue(weakset.delete(o, p, q), "o is found and deleted, so should return true, ignores p and q"); |
| } |
| }, |
| |
| { |
| name: "Delete should return true if item was in the WeakSet, false if not", |
| body: function () { |
| var weakset = new WeakSet(); |
| var o = { }; |
| var p = { }; |
| |
| weakset.add(o); |
| |
| assert.isFalse(weakset.delete(p), "p is not a key in the weakset, delete should return false"); |
| assert.isTrue(weakset.delete(o), "o is a key in the weakset, delete should remove it and return true"); |
| assert.isFalse(weakset.delete(o), "o is no longer a key in the weakset, delete should now return false"); |
| } |
| }, |
| |
| { |
| name: "Adding the same key twice is valid, having no affect on the second add", |
| body: function () { |
| var weakset = new WeakSet(); |
| |
| var o = { }; |
| var p = { }; |
| |
| weakset.add(o); |
| weakset.add(o); |
| weakset.add(p); |
| weakset.delete(o); |
| weakset.add(p); |
| weakset.add(o); |
| weakset.add(o); |
| |
| weakset.delete(o); |
| weakset.delete(p); |
| |
| weakset.add(o); |
| assert.isTrue(weakset.has(o), "o is in the weakset"); |
| weakset.add(o); |
| assert.isTrue(weakset.has(o), "o is still in the weakset"); |
| weakset.add(p); |
| assert.isTrue(weakset.has(o), "o is still in the weakset"); |
| assert.isTrue(weakset.has(p), "p is now in the weakset"); |
| weakset.delete(o); |
| assert.isFalse(weakset.has(o), "o is no longer in the weakset"); |
| assert.isTrue(weakset.has(p), "p is still in the weakset"); |
| weakset.add(p); |
| assert.isTrue(weakset.has(p), "p is still in the weakset"); |
| } |
| }, |
| |
| { |
| name: "add returns the weakset instance itself", |
| body: function () { |
| var weakset = new WeakSet(); |
| var o = { }; |
| |
| assert.areEqual(weakset, weakset.add(o), "Adding a new key should return WeakSet instance"); |
| assert.areEqual(weakset, weakset.add(o), "Adding an existing key should return WeakSet instance"); |
| } |
| }, |
| |
| { |
| name: "Adding and removing keys from one WeakSet shouldn't affect other WeakSets", |
| body: function () { |
| var ws1 = new WeakSet(); |
| var ws2 = new WeakSet(); |
| var ws3 = new WeakSet(); |
| |
| var o = { }; |
| var p = { }; |
| var q = { }; |
| |
| ws1.add(o); |
| ws1.add(p); |
| ws2.add(q); |
| |
| assert.isTrue(ws1.has(o), "ws1 has o"); |
| assert.isFalse(ws2.has(o), "ws2 does not have o"); |
| assert.isFalse(ws3.has(o), "ws3 does not have o"); |
| |
| assert.isTrue(ws1.has(p), "ws1 has p"); |
| assert.isTrue(ws2.has(q), "ws2 has q"); |
| assert.isFalse(ws1.has(q), "ws1 does not have q"); |
| assert.isFalse(ws2.has(p), "ws2 does not have p"); |
| assert.isFalse(ws3.has(p), "ws3 does not have p"); |
| assert.isFalse(ws3.has(q), "ws3 does not have q"); |
| |
| ws3.add(p); |
| ws3.add(q); |
| |
| assert.isTrue(ws3.has(p), "ws3 now has p"); |
| assert.isTrue(ws3.has(q), "ws3 now has q"); |
| assert.isTrue(ws1.has(p), "ws1 still has p"); |
| assert.isFalse(ws2.has(p), "ws2 still does not have p"); |
| assert.isFalse(ws1.has(q), "ws1 still does not have q"); |
| assert.isTrue(ws2.has(q), "ws2 still has q"); |
| |
| assert.isTrue(ws1.delete(p), "p is removed from ws1"); |
| |
| assert.isFalse(ws1.has(p), "ws1 no longer has p"); |
| assert.isTrue(ws3.has(p), "ws3 still has p"); |
| |
| ws3.delete(p); |
| ws3.delete(q); |
| |
| assert.isFalse(ws3.has(p), "ws3 no longer has p"); |
| assert.isFalse(ws3.has(q), "ws3 no longer has q"); |
| assert.isTrue(ws1.has(o), "ws1 still has o"); |
| assert.isTrue(ws2.has(q), "ws2 still has q"); |
| } |
| }, |
| |
| { |
| name: "Number, Boolean, and String and other special objects should all as keys", |
| body: function () { |
| var weakset = new WeakSet(); |
| |
| var n = new Number(1); |
| var b = new Boolean(2); |
| var s = new String("Hi"); |
| |
| /* |
| Fast DOM and HostDispatch objects are tested in the mshtml test weakset_DOMkey.html |
| WinRT objects are still an open issue; they are CustomExternalObjects so they work, |
| but they are proxied and the proxies are not kept alive by the outside object, only |
| by internal JS references. Further, allowing objects to be linked to the lifetime |
| of a WinJS object can cause cycles between JS GC objects and WinRT COM ref counted |
| objects, which are not deducible by the GC. Therefore using WinRT objects with |
| WeakSet is prone to subtle easy to make memory leak bugs. |
| var fd = new FastDOM(); |
| var hd = new HostDispatch(); |
| var wrt = new WinRT(); |
| */ |
| |
| var ab = new ArrayBuffer(32); |
| |
| weakset.add(n); |
| weakset.add(b); |
| weakset.add(s); |
| weakset.add(ab); |
| |
| assert.isTrue(weakset.has(n), "weakset has key n which is a Number instance"); |
| assert.isTrue(weakset.has(b), "weakset has key b which is a Boolean instance"); |
| assert.isTrue(weakset.has(s), "weakset has key s which is a String instance"); |
| assert.isTrue(weakset.has(ab), "weakset has key ab which is an ArrayBuffer instance"); |
| |
| assert.isTrue(weakset.delete(n), "Successfully delete key n which is a Number instance from weakset"); |
| assert.isTrue(weakset.delete(b), "Successfully delete key b which is a Boolean instance from weakset"); |
| assert.isTrue(weakset.delete(s), "Successfully delete key s which is a String instance from weakset"); |
| assert.isTrue(weakset.delete(ab), "Successfully delete key ab which is an ArrayBuffer instance from weakset"); |
| |
| assert.isFalse(weakset.has(n), "weakset no longer has key n"); |
| assert.isFalse(weakset.has(b), "weakset no longer has key b"); |
| assert.isFalse(weakset.has(s), "weakset no longer has key s"); |
| assert.isFalse(weakset.has(ab), "weakset no longer has key ab"); |
| } |
| }, |
| |
| ]; |
| |
| testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); |