diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/tests/non262/Set | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/tests/non262/Set')
-rw-r--r-- | js/src/tests/non262/Set/browser.js | 0 | ||||
-rw-r--r-- | js/src/tests/non262/Set/difference.js | 462 | ||||
-rw-r--r-- | js/src/tests/non262/Set/intersection.js | 478 | ||||
-rw-r--r-- | js/src/tests/non262/Set/is-disjoint-from.js | 448 | ||||
-rw-r--r-- | js/src/tests/non262/Set/is-subset-of.js | 333 | ||||
-rw-r--r-- | js/src/tests/non262/Set/is-superset-of.js | 360 | ||||
-rw-r--r-- | js/src/tests/non262/Set/shell.js | 102 | ||||
-rw-r--r-- | js/src/tests/non262/Set/symmetric-difference.js | 303 | ||||
-rw-r--r-- | js/src/tests/non262/Set/union.js | 300 |
9 files changed, 2786 insertions, 0 deletions
diff --git a/js/src/tests/non262/Set/browser.js b/js/src/tests/non262/Set/browser.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/js/src/tests/non262/Set/browser.js diff --git a/js/src/tests/non262/Set/difference.js b/js/src/tests/non262/Set/difference.js new file mode 100644 index 0000000000..2f42f14e96 --- /dev/null +++ b/js/src/tests/non262/Set/difference.js @@ -0,0 +1,462 @@ +// |reftest| shell-option(--enable-new-set-methods) skip-if(!Set.prototype.difference) + +assertEq(typeof Set.prototype.difference, "function"); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.difference, "length"), { + value: 1, writable: false, enumerable: false, configurable: true, +}); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.difference, "name"), { + value: "difference", writable: false, enumerable: false, configurable: true, +}); + +const emptySet = new Set(); +const emptySetLike = new SetLike(); +const emptyMap = new Map(); + +function asMap(values) { + return new Map(values.map(v => [v, v])); +} + +// Difference of two empty sets is an empty set. +assertSetContainsExactOrderedItems(emptySet.difference(emptySet), []); +assertSetContainsExactOrderedItems(emptySet.difference(emptySetLike), []); +assertSetContainsExactOrderedItems(emptySet.difference(emptyMap), []); + +// Test native Set, Set-like, and Map objects. +for (let values of [ + [], [1], [1, 2], [1, true, null, {}], +]) { + // Difference with an empty set. + assertSetContainsExactOrderedItems(new Set(values).difference(emptySet), values); + assertSetContainsExactOrderedItems(new Set(values).difference(emptySetLike), values); + assertSetContainsExactOrderedItems(new Set(values).difference(emptyMap), values); + assertSetContainsExactOrderedItems(emptySet.difference(new Set(values)), []); + assertSetContainsExactOrderedItems(emptySet.difference(new SetLike(values)), []); + assertSetContainsExactOrderedItems(emptySet.difference(asMap(values)), []); + + // Two sets with the exact same values. + assertSetContainsExactOrderedItems(new Set(values).difference(new Set(values)), []); + assertSetContainsExactOrderedItems(new Set(values).difference(new SetLike(values)), []); + assertSetContainsExactOrderedItems(new Set(values).difference(asMap(values)), []); + + // Difference of the same set object. + let set = new Set(values); + assertSetContainsExactOrderedItems(set.difference(set), []); +} + +// Check property accesses are in the correct order. +{ + let log = []; + + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next() { + log.push("next()"); + return {done: true}; + } + }, log); + + let {proxy: setLike} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + log.length = 0; + assertSetContainsExactOrderedItems(emptySet.difference(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Input has more elements than the this-value. + sizeValue = 1; + + log.length = 0; + assertSetContainsExactOrderedItems(emptySet.difference(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Input has fewer elements than the this-value. + sizeValue = 0; + + log.length = 0; + assertSetContainsExactOrderedItems(new Set([1]).difference(setLike), [1]); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + "next()", + ]); +} + +// Check input validation. +{ + let log = []; + + const nonCallable = {}; + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next: nonCallable, + }, log); + + let {proxy: setLike, obj: setLikeObj} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + log.length = 0; + assertSetContainsExactOrderedItems(emptySet.difference(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + log.length = 0; + assertThrowsInstanceOf(() => new Set([1]).difference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + ]); + + // Change |keys| to return a non-object value. + setLikeObj.keys = () => 123; + + log.length = 0; + assertSetContainsExactOrderedItems(emptySet.difference(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + log.length = 0; + assertThrowsInstanceOf(() => new Set([1]).difference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |keys| to a non-callable value. + setLikeObj.keys = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.difference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |has| to a non-callable value. + setLikeObj.has = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.difference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + ]); + + // Change |size| to NaN. + sizeValue = NaN; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.difference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); + + // Change |size| to undefined. + sizeValue = undefined; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.difference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); +} + +// Doesn't accept Array as an input. +assertThrowsInstanceOf(() => emptySet.difference([]), TypeError); + +// Works with Set subclasses. +{ + class MySet extends Set {} + + let myEmptySet = new MySet; + + assertSetContainsExactOrderedItems(emptySet.difference(myEmptySet), []); + assertSetContainsExactOrderedItems(myEmptySet.difference(myEmptySet), []); + assertSetContainsExactOrderedItems(myEmptySet.difference(emptySet), []); +} + +// Doesn't access any properties on the this-value. +{ + let log = []; + + let {proxy: setProto} = LoggingProxy(Set.prototype, log); + + let set = new Set([1, 2, 3]); + Object.setPrototypeOf(set, setProto); + + assertSetContainsExactOrderedItems(Set.prototype.difference.call(set, emptySet), [1, 2, 3]); + + assertEqArray(log, []); +} + +// Throws a TypeError when the this-value isn't a Set object. +for (let thisValue of [ + null, undefined, true, "", {}, new Map, new Proxy(new Set, {}), +]) { + assertThrowsInstanceOf(() => Set.prototype.difference.call(thisValue, emptySet), TypeError); +} + +// Doesn't return the original Set object. +{ + let set = new Set([1]); + assertEq(set.difference(emptySet) !== set, true); + assertEq(set.difference(new Set([2])) !== set, true); +} + +// Test insertion order +{ + let set = new Set([1, 2, 3]); + + // Case 1: Input is empty. + assertSetContainsExactOrderedItems(set.difference(new Set([])), [1, 2, 3]); + + // Case 2: Input has fewer elements. + assertSetContainsExactOrderedItems(set.difference(new Set([1, 2])), [3]); + assertSetContainsExactOrderedItems(set.difference(new Set([2, 1])), [3]); + assertSetContainsExactOrderedItems(set.difference(new Set([11, 2])), [1, 3]); + assertSetContainsExactOrderedItems(set.difference(new Set([2, 11])), [1, 3]); + + // Case 3: Input has same number of elements. + assertSetContainsExactOrderedItems(set.difference(new Set([1, 2, 3])), []); + assertSetContainsExactOrderedItems(set.difference(new Set([2, 3, 1])), []); + assertSetContainsExactOrderedItems(set.difference(new Set([3, 2, 1])), []); + assertSetContainsExactOrderedItems(set.difference(new Set([11, 2, 3])), [1]); + assertSetContainsExactOrderedItems(set.difference(new Set([2, 3, 11])), [1]); + assertSetContainsExactOrderedItems(set.difference(new Set([3, 2, 11])), [1]); + + // Case 4: Input has more elements. + assertSetContainsExactOrderedItems(set.difference(new Set([2, 3, 4, 5])), [1]); + assertSetContainsExactOrderedItems(set.difference(new Set([4, 5, 2, 3])), [1]); + assertSetContainsExactOrderedItems(set.difference(new Set([5, 4, 3, 2])), [1]); +} + +// Calls |has| when the this-value has fewer or the same number of keys. +{ + const keys = [1, 2, 3]; + + for (let size of [keys.length, 100, Infinity]) { + let i = 0; + + let setLike = { + size, + has(v) { + assertEq(this, setLike); + assertEq(arguments.length, 1); + assertEq(i < keys.length, true); + assertEq(v, keys[i++]); + return true; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertSetContainsExactOrderedItems(new Set(keys).difference(setLike), []); + } +} + +// Calls |keys| when the this-value has more keys. +{ + const keys = [1, 2, 3]; + + for (let size of [0, 1, 2]) { + let i = 0; + + let setLike = { + size, + has() { + throw new Error("Unexpected call to |keys| method"); + }, + keys() { + assertEq(this, setLike); + assertEq(arguments.length, 0); + + let iterator = { + next() { + assertEq(this, iterator); + assertEq(arguments.length, 0); + if (i < keys.length) { + return { + done: false, + value: keys[i++], + }; + } + return { + done: true, + get value() { + throw new Error("Unexpected call to |value| getter"); + }, + }; + } + }; + + return iterator; + }, + }; + + assertSetContainsExactOrderedItems(new Set(keys).difference(setLike), []); + } +} + +// Test result set order when the this-value was modified. +{ + let originalKeys = null; + + let setLike = { + size: 100, + has(v) { + if (!originalKeys) { + assertSetContainsExactOrderedItems(set, [1, 2, 3, 4]); + + originalKeys = [...set.keys()]; + + // Remove all existing items. + set.clear(); + + // Add new keys 11 and 22. + set.add(11); + set.add(22); + } + + // |has| is called exactly once for each key. + assertEq(originalKeys.includes(v), true); + + originalKeys.splice(originalKeys.indexOf(v), 1); + + return true; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + let set = new Set([1, 2, 3, 4]); + + assertSetContainsExactOrderedItems(set.difference(setLike), []); + assertSetContainsExactOrderedItems(set, [11, 22]); + assertEqArray(originalKeys, []); +} +{ + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + *keys() { + assertSetContainsExactOrderedItems(set, [1, 2, 3, 4]); + + let originalKeys = [...set.keys()]; + + // Remove all existing items. + set.clear(); + + // Add new keys 11 and 22. + set.add(11); + set.add(22); + + // Yield the original keys of |set|. + yield* originalKeys; + }, + }; + + let set = new Set([1, 2, 3, 4]); + + assertSetContainsExactOrderedItems(set.difference(setLike), []); + assertSetContainsExactOrderedItems(set, [11, 22]); +} + +// Tests which modify any built-ins should appear last, because modifications may disable +// optimised code paths. + +// Doesn't call the built-in |Set.prototype.{has, keys, size}| functions. +const SetPrototypeHas = Object.getOwnPropertyDescriptor(Set.prototype, "has"); +const SetPrototypeKeys = Object.getOwnPropertyDescriptor(Set.prototype, "keys"); +const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size"); + +delete Set.prototype.has; +delete Set.prototype.keys; +delete Set.prototype.size; + +try { + let set = new Set([1, 2, 3]); + let other = new SetLike([1, 2, 3]); + assertSetContainsExactOrderedItems(set.difference(other), []); +} finally { + Object.defineProperty(Set.prototype, "has", SetPrototypeHas); + Object.defineProperty(Set.prototype, "keys", SetPrototypeKeys); + Object.defineProperty(Set.prototype, "size", SetPrototypeSize); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Set/intersection.js b/js/src/tests/non262/Set/intersection.js new file mode 100644 index 0000000000..dd0411c5a4 --- /dev/null +++ b/js/src/tests/non262/Set/intersection.js @@ -0,0 +1,478 @@ +// |reftest| shell-option(--enable-new-set-methods) skip-if(!Set.prototype.intersection) + +assertEq(typeof Set.prototype.intersection, "function"); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.intersection, "length"), { + value: 1, writable: false, enumerable: false, configurable: true, +}); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.intersection, "name"), { + value: "intersection", writable: false, enumerable: false, configurable: true, +}); + +const emptySet = new Set(); +const emptySetLike = new SetLike(); +const emptyMap = new Map(); + +function asMap(values) { + return new Map(values.map(v => [v, v])); +} + +// Intersection of two empty sets is an empty set. +assertSetContainsExactOrderedItems(emptySet.intersection(emptySet), []); +assertSetContainsExactOrderedItems(emptySet.intersection(emptySetLike), []); +assertSetContainsExactOrderedItems(emptySet.intersection(emptyMap), []); + +// Test native Set, Set-like, and Map objects. +for (let values of [ + [], [1], [1, 2], [1, true, null, {}], +]) { + // Intersection with an empty set. + assertSetContainsExactOrderedItems(new Set(values).intersection(emptySet), []); + assertSetContainsExactOrderedItems(new Set(values).intersection(emptySetLike), []); + assertSetContainsExactOrderedItems(new Set(values).intersection(emptyMap), []); + assertSetContainsExactOrderedItems(emptySet.intersection(new Set(values)), []); + assertSetContainsExactOrderedItems(emptySet.intersection(new SetLike(values)), []); + assertSetContainsExactOrderedItems(emptySet.intersection(asMap(values)), []); + + // Two sets with the exact same values. + assertSetContainsExactOrderedItems(new Set(values).intersection(new Set(values)), values); + assertSetContainsExactOrderedItems(new Set(values).intersection(new SetLike(values)), values); + assertSetContainsExactOrderedItems(new Set(values).intersection(asMap(values)), values); + + // Intersection of the same set object. + let set = new Set(values); + assertSetContainsExactOrderedItems(set.intersection(set), values); +} + +// Check property accesses are in the correct order. +{ + let log = []; + + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next() { + log.push("next()"); + return {done: true}; + } + }, log); + + let {proxy: setLike} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + log.length = 0; + assertSetContainsExactOrderedItems(emptySet.intersection(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Input has more elements than the this-value. + sizeValue = 1; + + log.length = 0; + assertSetContainsExactOrderedItems(emptySet.intersection(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Input has fewer elements than the this-value. + sizeValue = 0; + + log.length = 0; + assertSetContainsExactOrderedItems(new Set([1]).intersection(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + "next()", + ]); +} + +// Check input validation. +{ + let log = []; + + const nonCallable = {}; + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next: nonCallable, + }, log); + + let {proxy: setLike, obj: setLikeObj} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + log.length = 0; + assertSetContainsExactOrderedItems(emptySet.intersection(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + log.length = 0; + assertThrowsInstanceOf(() => new Set([1]).intersection(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + ]); + + // Change |keys| to return a non-object value. + setLikeObj.keys = () => 123; + + log.length = 0; + assertSetContainsExactOrderedItems(emptySet.intersection(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + log.length = 0; + assertThrowsInstanceOf(() => new Set([1]).intersection(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |keys| to a non-callable value. + setLikeObj.keys = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.intersection(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |has| to a non-callable value. + setLikeObj.has = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.intersection(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + ]); + + // Change |size| to NaN. + sizeValue = NaN; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.intersection(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); + + // Change |size| to undefined. + sizeValue = undefined; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.intersection(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); +} + +// Doesn't accept Array as an input. +assertThrowsInstanceOf(() => emptySet.intersection([]), TypeError); + +// Works with Set subclasses. +{ + class MySet extends Set {} + + let myEmptySet = new MySet; + + assertSetContainsExactOrderedItems(emptySet.intersection(myEmptySet), []); + assertSetContainsExactOrderedItems(myEmptySet.intersection(myEmptySet), []); + assertSetContainsExactOrderedItems(myEmptySet.intersection(emptySet), []); +} + +// Doesn't access any properties on the this-value. +{ + let log = []; + + let {proxy: setProto} = LoggingProxy(Set.prototype, log); + + let set = new Set([1, 2, 3]); + Object.setPrototypeOf(set, setProto); + + assertSetContainsExactOrderedItems(Set.prototype.intersection.call(set, emptySet), []); + + assertEqArray(log, []); +} + +// Throws a TypeError when the this-value isn't a Set object. +for (let thisValue of [ + null, undefined, true, "", {}, new Map, new Proxy(new Set, {}), +]) { + assertThrowsInstanceOf(() => Set.prototype.intersection.call(thisValue, emptySet), TypeError); +} + +// Doesn't return the original Set object. +{ + let set = new Set([1]); + assertEq(set.intersection(emptySet) !== set, true); + assertEq(set.intersection(new Set([2])) !== set, true); +} + +// Test insertion order +{ + let set = new Set([1, 2, 3]); + + // Case 1: Input is empty. + assertSetContainsExactOrderedItems(set.intersection(new Set([])), []); + + // Case 2: Input has fewer elements. + assertSetContainsExactOrderedItems(set.intersection(new Set([1, 2])), [1, 2]); + assertSetContainsExactOrderedItems(set.intersection(new Set([2, 1])), [2, 1]); + assertSetContainsExactOrderedItems(set.intersection(new Set([11, 2])), [2]); + assertSetContainsExactOrderedItems(set.intersection(new Set([2, 11])), [2]); + + // Case 3: Input has same number of elements. + assertSetContainsExactOrderedItems(set.intersection(new Set([1, 2, 3])), [1, 2, 3]); + assertSetContainsExactOrderedItems(set.intersection(new Set([2, 3, 1])), [1, 2, 3]); + assertSetContainsExactOrderedItems(set.intersection(new Set([3, 2, 1])), [1, 2, 3]); + assertSetContainsExactOrderedItems(set.intersection(new Set([11, 2, 3])), [2, 3]); + assertSetContainsExactOrderedItems(set.intersection(new Set([2, 3, 11])), [2, 3]); + assertSetContainsExactOrderedItems(set.intersection(new Set([3, 2, 11])), [2, 3]); + + // Case 4: Input has more elements. + assertSetContainsExactOrderedItems(set.intersection(new Set([2, 3, 4, 5])), [2, 3]); + assertSetContainsExactOrderedItems(set.intersection(new Set([4, 5, 2, 3])), [2, 3]); + assertSetContainsExactOrderedItems(set.intersection(new Set([5, 4, 3, 2])), [2, 3]); +} + +// Calls |has| when the this-value has fewer or the same number of keys. +{ + const keys = [1, 2, 3]; + + for (let size of [keys.length, 100, Infinity]) { + let i = 0; + + let setLike = { + size, + has(v) { + assertEq(this, setLike); + assertEq(arguments.length, 1); + assertEq(i < keys.length, true); + assertEq(v, keys[i++]); + return true; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertSetContainsExactOrderedItems(new Set(keys).intersection(setLike), keys); + } +} + +// Calls |keys| when the this-value has more keys. +{ + const keys = [1, 2, 3]; + + for (let size of [0, 1, 2]) { + let i = 0; + + let setLike = { + size, + has() { + throw new Error("Unexpected call to |keys| method"); + }, + keys() { + assertEq(this, setLike); + assertEq(arguments.length, 0); + + let iterator = { + next() { + assertEq(this, iterator); + assertEq(arguments.length, 0); + if (i < keys.length) { + return { + done: false, + value: keys[i++], + }; + } + return { + done: true, + get value() { + throw new Error("Unexpected call to |value| getter"); + }, + }; + } + }; + + return iterator; + }, + }; + + assertSetContainsExactOrderedItems(new Set(keys).intersection(setLike), keys); + } +} + +// Test result set order when the this-value was modified. +{ + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + *keys() { + // Yield the same keys as in |set|. + yield* set.keys(); + + // Remove all existing items. + set.clear(); + + // Re-add keys 2 and 3, but in reversed order. + set.add(3); + set.add(2); + + // Additionally add 99. + set.add(99); + }, + }; + + let set = new Set([1, 2, 3, 4]); + + assertSetContainsExactOrderedItems(set.intersection(setLike), [1, 2, 3, 4]); + assertSetContainsExactOrderedItems(set, [3, 2, 99]); +} +{ + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + *keys() { + // Yield the same keys as in |set|. + yield* set.keys(); + + // Remove only keys 2 and 3. + set.delete(2); + set.delete(3); + + // Re-add keys 2 and 3, but in reversed order. + set.add(3); + set.add(2); + }, + }; + + let set = new Set([1, 2, 3, 4]); + + assertSetContainsExactOrderedItems(set.intersection(setLike), [1, 2, 3, 4]); + assertSetContainsExactOrderedItems(set, [1, 4, 3, 2]); +} + +// Test the same item can't be added multiple times. +{ + let seen = []; + + let setLike = { + size: 100, + has(v) { + // Remove and then re-add 2. + if (v === 2 && !seen.includes(v)) { + set.delete(v); + set.add(v); + } + + // Remember all visited keys. + seen.push(v); + + return true; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + let set = new Set([1, 2, 3]); + + assertSetContainsExactOrderedItems(set.intersection(setLike), [1, 2, 3]); + assertEqArray(seen, [1, 2, 3, 2]); +} + +// Tests which modify any built-ins should appear last, because modifications may disable +// optimised code paths. + +// Doesn't call the built-in |Set.prototype.{has, keys, size}| functions. +const SetPrototypeHas = Object.getOwnPropertyDescriptor(Set.prototype, "has"); +const SetPrototypeKeys = Object.getOwnPropertyDescriptor(Set.prototype, "keys"); +const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size"); + +delete Set.prototype.has; +delete Set.prototype.keys; +delete Set.prototype.size; + +try { + let set = new Set([1, 2, 3]); + let other = new SetLike([1, 2, 3]); + assertSetContainsExactOrderedItems(set.intersection(other), [1, 2, 3]); +} finally { + Object.defineProperty(Set.prototype, "has", SetPrototypeHas); + Object.defineProperty(Set.prototype, "keys", SetPrototypeKeys); + Object.defineProperty(Set.prototype, "size", SetPrototypeSize); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Set/is-disjoint-from.js b/js/src/tests/non262/Set/is-disjoint-from.js new file mode 100644 index 0000000000..e518accda4 --- /dev/null +++ b/js/src/tests/non262/Set/is-disjoint-from.js @@ -0,0 +1,448 @@ +// |reftest| shell-option(--enable-new-set-methods) skip-if(!Set.prototype.isDisjointFrom) + +assertEq(typeof Set.prototype.isDisjointFrom, "function"); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.isDisjointFrom, "length"), { + value: 1, writable: false, enumerable: false, configurable: true, +}); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.isDisjointFrom, "name"), { + value: "isDisjointFrom", writable: false, enumerable: false, configurable: true, +}); + +const emptySet = new Set(); +const emptySetLike = new SetLike(); +const emptyMap = new Map(); + +function asMap(values) { + return new Map(values.map(v => [v, v])); +} + +// Empty set is disjoint from the empty set. +assertEq(emptySet.isDisjointFrom(emptySet), true); +assertEq(emptySet.isDisjointFrom(emptySetLike), true); +assertEq(emptySet.isDisjointFrom(emptyMap), true); + +// Test native Set, Set-like, and Map objects. +for (let values of [ + [], [1], [1, 2], [1, true, null, {}], +]) { + // Disjoint operation with an empty set. + assertEq(new Set(values).isDisjointFrom(emptySet), true); + assertEq(new Set(values).isDisjointFrom(emptySetLike), true); + assertEq(new Set(values).isDisjointFrom(emptyMap), true); + assertEq(emptySet.isDisjointFrom(new Set(values)), true); + assertEq(emptySet.isDisjointFrom(new SetLike(values)), true); + assertEq(emptySet.isDisjointFrom(asMap(values)), true); + + // Two sets with the exact same values. + assertEq(new Set(values).isDisjointFrom(new Set(values)), values.length === 0); + assertEq(new Set(values).isDisjointFrom(new SetLike(values)), values.length === 0); + assertEq(new Set(values).isDisjointFrom(asMap(values)), values.length === 0); + + // Disjoint operation of the same set object. + let set = new Set(values); + assertEq(set.isDisjointFrom(set), values.length === 0); +} + +// Check property accesses are in the correct order. +{ + let log = []; + + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next() { + log.push("next()"); + return {done: true}; + } + }, log); + + let {proxy: setLike} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + assertEq(emptySet.isDisjointFrom(setLike), true); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // |keys| is called when the this-value has more elements. + + log.length = 0; + assertEq(new Set([1]).isDisjointFrom(setLike), true); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + "next()", + ]); +} + +// Check input validation. +{ + let log = []; + + const nonCallable = {}; + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next: nonCallable, + }, log); + + let {proxy: setLike, obj: setLikeObj} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + log.length = 0; + assertEq(emptySet.isDisjointFrom(setLike), true); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + log.length = 0; + assertThrowsInstanceOf(() => new Set([1]).isDisjointFrom(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + ]); + + // Change |keys| to return a non-object value. + setLikeObj.keys = () => 123; + + log.length = 0; + assertEq(emptySet.isDisjointFrom(setLike), true); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + log.length = 0; + assertThrowsInstanceOf(() => new Set([1]).isDisjointFrom(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |keys| to a non-callable value. + setLikeObj.keys = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isDisjointFrom(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |has| to a non-callable value. + setLikeObj.has = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isDisjointFrom(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + ]); + + // Change |size| to NaN. + sizeValue = NaN; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isDisjointFrom(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); + + // Change |size| to undefined. + sizeValue = undefined; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isDisjointFrom(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); +} + +// Doesn't accept Array as an input. +assertThrowsInstanceOf(() => emptySet.isDisjointFrom([]), TypeError); + +// Works with Set subclasses. +{ + class MySet extends Set {} + + let myEmptySet = new MySet; + + assertEq(emptySet.isDisjointFrom(myEmptySet), true); + assertEq(myEmptySet.isDisjointFrom(myEmptySet), true); + assertEq(myEmptySet.isDisjointFrom(emptySet), true); +} + +// Doesn't access any properties on the this-value. +{ + let log = []; + + let {proxy: setProto} = LoggingProxy(Set.prototype, log); + + let set = new Set([]); + Object.setPrototypeOf(set, setProto); + + assertEq(Set.prototype.isDisjointFrom.call(set, emptySet), true); + + assertEqArray(log, []); +} + +// Throws a TypeError when the this-value isn't a Set object. +for (let thisValue of [ + null, undefined, true, "", {}, new Map, new Proxy(new Set, {}), +]) { + assertThrowsInstanceOf(() => Set.prototype.isDisjointFrom.call(thisValue, emptySet), TypeError); +} + +// Calls |has| when the this-value has fewer keys. +{ + const keys = [1, 2, 3]; + + for (let size of [keys.length, 100, Infinity]) { + let i = 0; + + let setLike = { + size, + has(v) { + assertEq(this, setLike); + assertEq(arguments.length, 1); + assertEq(i < keys.length, true); + assertEq(v, keys[i++]); + return false; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertEq(new Set([1, 2, 3]).isDisjointFrom(setLike), true); + } +} + +// Calls |keys| when the this-value has more keys. +{ + const keys = [1, 2, 3]; + + for (let size of [0, 1, 2]) { + let setLike = { + size, + has() { + throw new Error("Unexpected call to |has| method"); + }, + *keys() { + yield* [4, 5, 6]; + }, + }; + + assertEq(new Set(keys).isDisjointFrom(setLike), true); + } + + // Also test early return after first match. + for (let size of [0, 1, 2]) { + let setLike = { + size, + has() { + throw new Error("Unexpected call to |has| method"); + }, + *keys() { + yield keys[0]; + + throw new Error("keys iterator called too many times"); + }, + }; + + assertEq(new Set(keys).isDisjointFrom(setLike), false); + } +} + +// Test when this-value is modified during iteration. +{ + let set = new Set([]); + + // |setLike| has more entries than |set|. + let setLike = { + size: 100, + has(v) { + assertEq(set.has(v), true); + set.delete(v); + return false; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertEq(set.isDisjointFrom(setLike), true); + assertSetContainsExactOrderedItems(set, []); +} +{ + let set = new Set([0]); + + let keys = [1, 2, 3]; + + let lastValue; + let keysIter = { + next() { + if (lastValue !== undefined) { + assertEq(set.has(lastValue), false); + set.add(lastValue); + + lastValue = undefined; + } + + if (keys.length) { + let value = keys.shift(); + lastValue = value; + return { + done: false, + get value() { + return value; + } + }; + } + return { + done: true, + get value() { + throw new Error("Unexpected call to |value| getter"); + } + }; + } + }; + + // |setLike| has fewer entries than |set|. + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + return keysIter; + }, + }; + + assertEq(set.isDisjointFrom(setLike), true); + assertSetContainsExactOrderedItems(set, [0, 1, 2, 3]); +} + +// IteratorClose is called for early returns. +{ + let log = []; + + let keysIter = { + next() { + log.push("next"); + return {done: false, value: 1}; + }, + return() { + log.push("return"); + return { + get value() { throw new Error("Unexpected call to |value| getter"); }, + get done() { throw new Error("Unexpected call to |done| getter"); }, + }; + } + }; + + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + return keysIter; + }, + }; + + assertEq(new Set([1, 2, 3]).isDisjointFrom(setLike), false); + + assertEqArray(log, ["next", "return"]); +} + +// IteratorClose isn't called for non-early returns. +{ + let setLike = new SetLike([4, 5, 6]); + + assertEq(new Set([1, 2, 3]).isDisjointFrom(setLike), true); +} + +// Tests which modify any built-ins should appear last, because modifications may disable +// optimised code paths. + +// Doesn't call the built-in |Set.prototype.{has, keys, size}| functions. +const SetPrototypeHas = Object.getOwnPropertyDescriptor(Set.prototype, "has"); +const SetPrototypeKeys = Object.getOwnPropertyDescriptor(Set.prototype, "keys"); +const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size"); + +delete Set.prototype.has; +delete Set.prototype.keys; +delete Set.prototype.size; + +try { + let set = new Set([1, 2, 3]); + let other = new SetLike([1, 2, 3]); + assertEq(set.isDisjointFrom(other), false); +} finally { + Object.defineProperty(Set.prototype, "has", SetPrototypeHas); + Object.defineProperty(Set.prototype, "keys", SetPrototypeKeys); + Object.defineProperty(Set.prototype, "size", SetPrototypeSize); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Set/is-subset-of.js b/js/src/tests/non262/Set/is-subset-of.js new file mode 100644 index 0000000000..2847bb4944 --- /dev/null +++ b/js/src/tests/non262/Set/is-subset-of.js @@ -0,0 +1,333 @@ +// |reftest| shell-option(--enable-new-set-methods) skip-if(!Set.prototype.isSubsetOf) + +assertEq(typeof Set.prototype.isSubsetOf, "function"); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.isSubsetOf, "length"), { + value: 1, writable: false, enumerable: false, configurable: true, +}); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.isSubsetOf, "name"), { + value: "isSubsetOf", writable: false, enumerable: false, configurable: true, +}); + +const emptySet = new Set(); +const emptySetLike = new SetLike(); +const emptyMap = new Map(); + +function asMap(values) { + return new Map(values.map(v => [v, v])); +} + +// Empty set is a subset of the empty set. +assertEq(emptySet.isSubsetOf(emptySet), true); +assertEq(emptySet.isSubsetOf(emptySetLike), true); +assertEq(emptySet.isSubsetOf(emptyMap), true); + +// Test native Set, Set-like, and Map objects. +for (let values of [ + [], [1], [1, 2], [1, true, null, {}], +]) { + // Subset operation with an empty set. + assertEq(new Set(values).isSubsetOf(emptySet), values.length === 0); + assertEq(new Set(values).isSubsetOf(emptySetLike), values.length === 0); + assertEq(new Set(values).isSubsetOf(emptyMap), values.length === 0); + assertEq(emptySet.isSubsetOf(new Set(values)), true); + assertEq(emptySet.isSubsetOf(new SetLike(values)), true); + assertEq(emptySet.isSubsetOf(asMap(values)), true); + + // Two sets with the exact same values. + assertEq(new Set(values).isSubsetOf(new Set(values)), true); + assertEq(new Set(values).isSubsetOf(new SetLike(values)), true); + assertEq(new Set(values).isSubsetOf(asMap(values)), true); + + // Subset operation of the same set object. + let set = new Set(values); + assertEq(set.isSubsetOf(set), true); +} + +// Check property accesses are in the correct order. +{ + let log = []; + + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next() { + log.push("next()"); + return {done: true}; + } + }, log); + + let {proxy: setLike} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + assertEq(emptySet.isSubsetOf(setLike), true); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); +} + +// Check input validation. +{ + let log = []; + + const nonCallable = {}; + let sizeValue = 0; + + let {proxy: setLike, obj: setLikeObj} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }, log); + + // Change |keys| to a non-callable value. + setLikeObj.keys = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSubsetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |has| to a non-callable value. + setLikeObj.has = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSubsetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + ]); + + // Change |size| to NaN. + sizeValue = NaN; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSubsetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); + + // Change |size| to undefined. + sizeValue = undefined; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSubsetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); +} + +// Doesn't accept Array as an input. +assertThrowsInstanceOf(() => emptySet.isSubsetOf([]), TypeError); + +// Works with Set subclasses. +{ + class MySet extends Set {} + + let myEmptySet = new MySet; + + assertEq(emptySet.isSubsetOf(myEmptySet), true); + assertEq(myEmptySet.isSubsetOf(myEmptySet), true); + assertEq(myEmptySet.isSubsetOf(emptySet), true); +} + +// Doesn't access any properties on the this-value. +{ + let log = []; + + let {proxy: setProto} = LoggingProxy(Set.prototype, log); + + let set = new Set([]); + Object.setPrototypeOf(set, setProto); + + assertEq(Set.prototype.isSubsetOf.call(set, emptySet), true); + + assertEqArray(log, []); +} + +// Throws a TypeError when the this-value isn't a Set object. +for (let thisValue of [ + null, undefined, true, "", {}, new Map, new Proxy(new Set, {}), +]) { + assertThrowsInstanceOf(() => Set.prototype.isSubsetOf.call(thisValue, emptySet), TypeError); +} + +// Doesn't call |has| when this-value has more elements. +{ + let set = new Set([1, 2, 3]); + + for (let size of [0, 1, 2]) { + let setLike = { + size, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertEq(set.isSubsetOf(setLike), false); + } +} + +// Test when this-value is modified during iteration. +{ + let set = new Set([1, 2, 3]); + + let seen = new Set(); + + let setLike = { + size: 100, + has(v) { + assertEq(arguments.length, 1); + assertEq(this, setLike); + assertEq(set.has(v), true); + + assertEq(seen.has(v), false); + seen.add(v); + + // Delete the current element. + set.delete(v); + + return true; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertEq(set.isSubsetOf(setLike), true); + assertSetContainsExactOrderedItems(set, []); + assertSetContainsExactOrderedItems(seen, [1, 2, 3]); +} +{ + let set = new Set([1]); + + let seen = new Set(); + let newKeys = [2, 3, 4, 5]; + + let setLike = { + size: 100, + has(v) { + assertEq(arguments.length, 1); + assertEq(this, setLike); + assertEq(set.has(v), true); + + assertEq(seen.has(v), false); + seen.add(v); + + // Delete the current element. + set.delete(v); + + // Add new elements. + if (newKeys.length) { + set.add(newKeys.shift()); + } + + return true; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertEq(set.isSubsetOf(setLike), true); + assertSetContainsExactOrderedItems(set, []); + assertSetContainsExactOrderedItems(seen, [1, 2, 3, 4, 5]); + assertEq(newKeys.length, 0); +} +{ + let set = new Set([1, 2, 3]); + + let seen = new Set(); + let deleted = false; + + let setLike = { + size: 100, + has(v) { + assertEq(arguments.length, 1); + assertEq(this, setLike); + assertEq(set.has(v), true); + + assertEq(seen.has(v), false); + seen.add(v); + + if (!deleted) { + assertEq(v, 1); + set.delete(2); + deleted = true; + } + + return true; + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertEq(set.isSubsetOf(setLike), true); + assertSetContainsExactOrderedItems(set, [1, 3]); + assertSetContainsExactOrderedItems(seen, [1, 3]); + assertEq(deleted, true); +} + +// Tests which modify any built-ins should appear last, because modifications may disable +// optimised code paths. + +// Doesn't call the built-in |Set.prototype.{has, keys, size}| functions. +const SetPrototypeHas = Object.getOwnPropertyDescriptor(Set.prototype, "has"); +const SetPrototypeKeys = Object.getOwnPropertyDescriptor(Set.prototype, "keys"); +const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size"); + +delete Set.prototype.has; +delete Set.prototype.keys; +delete Set.prototype.size; + +try { + let set = new Set([1, 2, 3]); + let other = new SetLike([1, 2, 3]); + assertEq(set.isSubsetOf(other), true); +} finally { + Object.defineProperty(Set.prototype, "has", SetPrototypeHas); + Object.defineProperty(Set.prototype, "keys", SetPrototypeKeys); + Object.defineProperty(Set.prototype, "size", SetPrototypeSize); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Set/is-superset-of.js b/js/src/tests/non262/Set/is-superset-of.js new file mode 100644 index 0000000000..c0728697fe --- /dev/null +++ b/js/src/tests/non262/Set/is-superset-of.js @@ -0,0 +1,360 @@ +// |reftest| shell-option(--enable-new-set-methods) skip-if(!Set.prototype.isSupersetOf) + +assertEq(typeof Set.prototype.isSupersetOf, "function"); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.isSupersetOf, "length"), { + value: 1, writable: false, enumerable: false, configurable: true, +}); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.isSupersetOf, "name"), { + value: "isSupersetOf", writable: false, enumerable: false, configurable: true, +}); + +const emptySet = new Set(); +const emptySetLike = new SetLike(); +const emptyMap = new Map(); + +function asMap(values) { + return new Map(values.map(v => [v, v])); +} + +// Empty set is a superset of the empty set. +assertEq(emptySet.isSupersetOf(emptySet), true); +assertEq(emptySet.isSupersetOf(emptySetLike), true); +assertEq(emptySet.isSupersetOf(emptyMap), true); + +// Test native Set, Set-like, and Map objects. +for (let values of [ + [], [1], [1, 2], [1, true, null, {}], +]) { + // Superset operation with an empty set. + assertEq(new Set(values).isSupersetOf(emptySet), true); + assertEq(new Set(values).isSupersetOf(emptySetLike), true); + assertEq(new Set(values).isSupersetOf(emptyMap), true); + assertEq(emptySet.isSupersetOf(new Set(values)), values.length === 0); + assertEq(emptySet.isSupersetOf(new SetLike(values)), values.length === 0); + assertEq(emptySet.isSupersetOf(asMap(values)), values.length === 0); + + // Two sets with the exact same values. + assertEq(new Set(values).isSupersetOf(new Set(values)), true); + assertEq(new Set(values).isSupersetOf(new SetLike(values)), true); + assertEq(new Set(values).isSupersetOf(asMap(values)), true); + + // Superset operation of the same set object. + let set = new Set(values); + assertEq(set.isSupersetOf(set), true); +} + +// Check property accesses are in the correct order. +{ + let log = []; + + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next() { + log.push("next()"); + return {done: true}; + } + }, log); + + let {proxy: setLike} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + assertEq(emptySet.isSupersetOf(setLike), true); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + "next()", + ]); + + // |keys| isn't called when the this-value has fewer elements. + sizeValue = 1; + + log.length = 0; + assertEq(emptySet.isSupersetOf(setLike), false); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); +} + +// Check input validation. +{ + let log = []; + + const nonCallable = {}; + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next: nonCallable, + }, log); + + let {proxy: setLike, obj: setLikeObj} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSupersetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + ]); + + // Change |keys| to return a non-object value. + setLikeObj.keys = () => 123; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSupersetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |keys| to a non-callable value. + setLikeObj.keys = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSupersetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |has| to a non-callable value. + setLikeObj.has = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSupersetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + ]); + + // Change |size| to NaN. + sizeValue = NaN; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSupersetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); + + // Change |size| to undefined. + sizeValue = undefined; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.isSupersetOf(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); +} + +// Doesn't accept Array as an input. +assertThrowsInstanceOf(() => emptySet.isSupersetOf([]), TypeError); + +// Works with Set subclasses. +{ + class MySet extends Set {} + + let myEmptySet = new MySet; + + assertEq(emptySet.isSupersetOf(myEmptySet), true); + assertEq(myEmptySet.isSupersetOf(myEmptySet), true); + assertEq(myEmptySet.isSupersetOf(emptySet), true); +} + +// Doesn't access any properties on the this-value. +{ + let log = []; + + let {proxy: setProto} = LoggingProxy(Set.prototype, log); + + let set = new Set([]); + Object.setPrototypeOf(set, setProto); + + assertEq(Set.prototype.isSupersetOf.call(set, emptySet), true); + + assertEqArray(log, []); +} + +// Throws a TypeError when the this-value isn't a Set object. +for (let thisValue of [ + null, undefined, true, "", {}, new Map, new Proxy(new Set, {}), +]) { + assertThrowsInstanceOf(() => Set.prototype.isSupersetOf.call(thisValue, emptySet), TypeError); +} + +// Doesn't call |has| nor |keys| when this-value has fewer elements. +{ + let set = new Set([1, 2, 3]); + + for (let size of [100, Infinity]) { + let setLike = { + size, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + throw new Error("Unexpected call to |keys| method"); + }, + }; + + assertEq(set.isSupersetOf(setLike), false); + } +} + +// Test when this-value is modified during iteration. +{ + let set = new Set([]); + + let keys = [1, 2, 3]; + + let keysIter = { + next() { + if (keys.length) { + let value = keys.shift(); + return { + done: false, + get value() { + assertEq(set.has(value), false); + set.add(value); + return value; + } + }; + } + return { + done: true, + get value() { + throw new Error("Unexpected call to |value| getter"); + } + }; + } + }; + + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + return keysIter; + }, + }; + + assertEq(set.isSupersetOf(setLike), true); + assertSetContainsExactOrderedItems(set, [1, 2, 3]); +} + +// IteratorClose is called for early returns. +{ + let log = []; + + let keysIter = { + next() { + log.push("next"); + return {done: false, value: 1}; + }, + return() { + log.push("return"); + return { + get value() { throw new Error("Unexpected call to |value| getter"); }, + get done() { throw new Error("Unexpected call to |done| getter"); }, + }; + } + }; + + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + return keysIter; + }, + }; + + assertEq(new Set([2, 3, 4]).isSupersetOf(setLike), false); + + assertEqArray(log, ["next", "return"]); +} + +// IteratorClose isn't called for non-early returns. +{ + let setLike = new SetLike([1, 2, 3]); + + assertEq(new Set([1, 2, 3]).isSupersetOf(setLike), true); +} + +// Tests which modify any built-ins should appear last, because modifications may disable +// optimised code paths. + +// Doesn't call the built-in |Set.prototype.{has, keys, size}| functions. +const SetPrototypeHas = Object.getOwnPropertyDescriptor(Set.prototype, "has"); +const SetPrototypeKeys = Object.getOwnPropertyDescriptor(Set.prototype, "keys"); +const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size"); + +delete Set.prototype.has; +delete Set.prototype.keys; +delete Set.prototype.size; + +try { + let set = new Set([1, 2, 3]); + let other = new SetLike([1, 2, 3]); + assertEq(set.isSupersetOf(other), true); +} finally { + Object.defineProperty(Set.prototype, "has", SetPrototypeHas); + Object.defineProperty(Set.prototype, "keys", SetPrototypeKeys); + Object.defineProperty(Set.prototype, "size", SetPrototypeSize); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Set/shell.js b/js/src/tests/non262/Set/shell.js new file mode 100644 index 0000000000..d6bd2e2083 --- /dev/null +++ b/js/src/tests/non262/Set/shell.js @@ -0,0 +1,102 @@ +(function(global) { + // Save the primordial values. + const {Array, Error, Object, Proxy, Reflect, Set} = global; + + const ArrayIsArray = Array.isArray; + const ReflectApply = Reflect.apply; + const ReflectDefineProperty = Reflect.defineProperty; + const ReflectGet = Reflect.get; + const ReflectGetPrototypeOf = Reflect.getPrototypeOf; + const SetPrototype = Set.prototype; + const SetPrototypeHas = SetPrototype.has; + const SetPrototypeSize = Object.getOwnPropertyDescriptor(SetPrototype, "size").get; + const SetPrototypeKeys = SetPrototype.keys; + const SetIteratorPrototypeNext = new Set().values().next; + + function assertSetContainsExactOrderedItems(actual, expected) { + assertEq(ReflectGetPrototypeOf(actual), SetPrototype, "actual is a native Set object"); + assertEq(ArrayIsArray(expected), true, "expected is an Array object"); + + assertEq(ReflectApply(SetPrototypeSize, actual, []), expected.length); + + let index = 0; + let keys = ReflectApply(SetPrototypeKeys, actual, []); + + while (true) { + let {done, value: item} = ReflectApply(SetIteratorPrototypeNext, keys, []); + if (done) { + break; + } + assertEq(item, expected[index], `Element at index ${index}:`); + index++; + } + } + global.assertSetContainsExactOrderedItems = assertSetContainsExactOrderedItems; + + class SetLike { + #set; + + constructor(values) { + this.#set = new Set(values); + } + + get size() { + return ReflectApply(SetPrototypeSize, this.#set, []); + } + + has(value) { + return ReflectApply(SetPrototypeHas, this.#set, [value]); + } + + keys() { + let keys = ReflectApply(SetPrototypeKeys, this.#set, []); + return new SetIteratorLike(keys); + } + } + global.SetLike = SetLike; + + class SetIteratorLike { + #keys; + + constructor(keys) { + this.#keys = keys; + } + + next() { + return ReflectApply(SetIteratorPrototypeNext, this.#keys, []); + } + + // The |return| method of the iterator protocol is never called. + return() { + throw new Error("Unexpected call to |return| method"); + } + + // The |throw| method of the iterator protocol is never called. + throw() { + throw new Error("Unexpected call to |throw| method"); + } + } + + function LoggingProxy(obj, log) { + assertEq(ArrayIsArray(log), true); + + let handler = new Proxy({ + get(t, pk, r) { + ReflectDefineProperty(log, log.length, { + value: pk, writable: true, enumerable: true, configurable: true, + }); + return ReflectGet(t, pk, r); + } + }, { + get(t, pk, r) { + ReflectDefineProperty(log, log.length, { + value: `[[${pk}]]`, writable: true, enumerable: true, configurable: true, + }); + return ReflectGet(t, pk, r); + } + }); + + return {obj, proxy: new Proxy(obj, handler)}; + } + global.LoggingProxy = LoggingProxy; +})(this); diff --git a/js/src/tests/non262/Set/symmetric-difference.js b/js/src/tests/non262/Set/symmetric-difference.js new file mode 100644 index 0000000000..a4182cbed3 --- /dev/null +++ b/js/src/tests/non262/Set/symmetric-difference.js @@ -0,0 +1,303 @@ +// |reftest| shell-option(--enable-new-set-methods) skip-if(!Set.prototype.symmetricDifference) + +assertEq(typeof Set.prototype.symmetricDifference, "function"); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.symmetricDifference, "length"), { + value: 1, writable: false, enumerable: false, configurable: true, +}); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.symmetricDifference, "name"), { + value: "symmetricDifference", writable: false, enumerable: false, configurable: true, +}); + +const emptySet = new Set(); +const emptySetLike = new SetLike(); +const emptyMap = new Map(); + +function asMap(values) { + return new Map(values.map(v => [v, v])); +} + +// Symmetric difference of two empty sets is an empty set. +assertSetContainsExactOrderedItems(emptySet.symmetricDifference(emptySet), []); +assertSetContainsExactOrderedItems(emptySet.symmetricDifference(emptySetLike), []); +assertSetContainsExactOrderedItems(emptySet.symmetricDifference(emptyMap), []); + +// Test native Set, Set-like, and Map objects. +for (let values of [ + [], [1], [1, 2], [1, true, null, {}], +]) { + // Symmetric difference with an empty set. + assertSetContainsExactOrderedItems(new Set(values).symmetricDifference(emptySet), values); + assertSetContainsExactOrderedItems(new Set(values).symmetricDifference(emptySetLike), values); + assertSetContainsExactOrderedItems(new Set(values).symmetricDifference(emptyMap), values); + assertSetContainsExactOrderedItems(emptySet.symmetricDifference(new Set(values)), values); + assertSetContainsExactOrderedItems(emptySet.symmetricDifference(new SetLike(values)), values); + assertSetContainsExactOrderedItems(emptySet.symmetricDifference(asMap(values)), values); + + // Two sets with the exact same values. + assertSetContainsExactOrderedItems(new Set(values).symmetricDifference(new Set(values)), []); + assertSetContainsExactOrderedItems(new Set(values).symmetricDifference(new SetLike(values)), []); + assertSetContainsExactOrderedItems(new Set(values).symmetricDifference(asMap(values)), []); + + // Symmetric difference of the same set object. + let set = new Set(values); + assertSetContainsExactOrderedItems(set.symmetricDifference(set), []); +} + +// Check property accesses are in the correct order. +{ + let log = []; + + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next() { + log.push("next()"); + return {done: true}; + } + }, log); + + let {proxy: setLike} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + assertSetContainsExactOrderedItems(emptySet.symmetricDifference(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + "next()", + ]); +} + +// Check input validation. +{ + let log = []; + + const nonCallable = {}; + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next: nonCallable, + }, log); + + let {proxy: setLike, obj: setLikeObj} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.symmetricDifference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + ]); + + // Change |keys| to return a non-object value. + setLikeObj.keys = () => 123; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.symmetricDifference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |keys| to a non-callable value. + setLikeObj.keys = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.symmetricDifference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |has| to a non-callable value. + setLikeObj.has = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.symmetricDifference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + ]); + + // Change |size| to NaN. + sizeValue = NaN; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.symmetricDifference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); + + // Change |size| to undefined. + sizeValue = undefined; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.symmetricDifference(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); +} + +// Doesn't accept Array as an input. +assertThrowsInstanceOf(() => emptySet.symmetricDifference([]), TypeError); + +// Works with Set subclasses. +{ + class MySet extends Set {} + + let myEmptySet = new MySet; + + assertSetContainsExactOrderedItems(emptySet.symmetricDifference(myEmptySet), []); + assertSetContainsExactOrderedItems(myEmptySet.symmetricDifference(myEmptySet), []); + assertSetContainsExactOrderedItems(myEmptySet.symmetricDifference(emptySet), []); +} + +// Doesn't access any properties on the this-value. +{ + let log = []; + + let {proxy: setProto} = LoggingProxy(Set.prototype, log); + + let set = new Set([1, 2, 3]); + Object.setPrototypeOf(set, setProto); + + assertSetContainsExactOrderedItems(Set.prototype.symmetricDifference.call(set, emptySet), [1, 2, 3]); + + assertEqArray(log, []); +} + +// Throws a TypeError when the this-value isn't a Set object. +for (let thisValue of [ + null, undefined, true, "", {}, new Map, new Proxy(new Set, {}), +]) { + assertThrowsInstanceOf(() => Set.prototype.symmetricDifference.call(thisValue, emptySet), TypeError); +} + +// Doesn't return the original Set object. +{ + let set = new Set([1]); + assertEq(set.symmetricDifference(emptySet) !== set, true); + assertEq(set.symmetricDifference(new Set([2])) !== set, true); +} + +// Test insertion order +{ + let set = new Set([1, 2]); + + // Case 1: Input is empty. + assertSetContainsExactOrderedItems(set.symmetricDifference(new Set([])), [1, 2]); + + // Case 2: Input has fewer elements. + assertSetContainsExactOrderedItems(set.symmetricDifference(new Set([3])), [1, 2, 3]); + assertSetContainsExactOrderedItems(set.symmetricDifference(new Set([2])), [1]); + + // Case 3: Input has same number of elements. + assertSetContainsExactOrderedItems(set.symmetricDifference(new Set([3, 4])), [1, 2, 3, 4]); + assertSetContainsExactOrderedItems(set.symmetricDifference(new Set([2, 3])), [1, 3]); + + // Case 4: Input has more elements. + assertSetContainsExactOrderedItems(set.symmetricDifference(new Set([3, 4, 5])), [1, 2, 3, 4, 5]); + assertSetContainsExactOrderedItems(set.symmetricDifference(new Set([2, 4, 5])), [1, 4, 5]); +} + +// Test that the input set is copied after accessing the |next| property of the keys iterator. +{ + let set = new Set([1, 2, 3]); + + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + return { + get next() { + // Clear the set when getting the |next| method. + set.clear(); + + // And then add a single new key. + set.add(4); + + return function() { + return {done: true}; + }; + } + }; + }, + }; + + // The result should consist of the single, newly added key. + assertSetContainsExactOrderedItems(set.symmetricDifference(setLike), [4]); +} + +// Tests which modify any built-ins should appear last, because modifications may disable +// optimised code paths. + +// Doesn't call the built-in |Set.prototype.{has, keys, size}| functions. +const SetPrototypeHas = Object.getOwnPropertyDescriptor(Set.prototype, "has"); +const SetPrototypeKeys = Object.getOwnPropertyDescriptor(Set.prototype, "keys"); +const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size"); + +delete Set.prototype.has; +delete Set.prototype.keys; +delete Set.prototype.size; + +try { + let set = new Set([1, 2, 3]); + let other = new SetLike([1, 2, 3]); + assertSetContainsExactOrderedItems(set.symmetricDifference(other), []); +} finally { + Object.defineProperty(Set.prototype, "has", SetPrototypeHas); + Object.defineProperty(Set.prototype, "keys", SetPrototypeKeys); + Object.defineProperty(Set.prototype, "size", SetPrototypeSize); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Set/union.js b/js/src/tests/non262/Set/union.js new file mode 100644 index 0000000000..0aef79822d --- /dev/null +++ b/js/src/tests/non262/Set/union.js @@ -0,0 +1,300 @@ +// |reftest| shell-option(--enable-new-set-methods) skip-if(!Set.prototype.union) + +assertEq(typeof Set.prototype.union, "function"); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.union, "length"), { + value: 1, writable: false, enumerable: false, configurable: true, +}); +assertDeepEq(Object.getOwnPropertyDescriptor(Set.prototype.union, "name"), { + value: "union", writable: false, enumerable: false, configurable: true, +}); + +const emptySet = new Set(); +const emptySetLike = new SetLike(); +const emptyMap = new Map(); + +function asMap(values) { + return new Map(values.map(v => [v, v])); +} + +// Union of two empty sets is an empty set. +assertSetContainsExactOrderedItems(emptySet.union(emptySet), []); +assertSetContainsExactOrderedItems(emptySet.union(emptySetLike), []); +assertSetContainsExactOrderedItems(emptySet.union(emptyMap), []); + +// Test native Set, Set-like, and Map objects. +for (let values of [ + [], [1], [1, 2], [1, true, null, {}], +]) { + // Union with an empty set. + assertSetContainsExactOrderedItems(new Set(values).union(emptySet), values); + assertSetContainsExactOrderedItems(new Set(values).union(emptySetLike), values); + assertSetContainsExactOrderedItems(new Set(values).union(emptyMap), values); + assertSetContainsExactOrderedItems(emptySet.union(new Set(values)), values); + assertSetContainsExactOrderedItems(emptySet.union(new SetLike(values)), values); + assertSetContainsExactOrderedItems(emptySet.union(asMap(values)), values); + + // Two sets with the exact same values. + assertSetContainsExactOrderedItems(new Set(values).union(new Set(values)), values); + assertSetContainsExactOrderedItems(new Set(values).union(new SetLike(values)), values); + assertSetContainsExactOrderedItems(new Set(values).union(asMap(values)), values); + + // Union of the same set object. + let set = new Set(values); + assertSetContainsExactOrderedItems(set.union(set), values); +} + +// Check property accesses are in the correct order. +{ + let log = []; + + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next() { + log.push("next()"); + return {done: true}; + } + }, log); + + let {proxy: setLike} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + assertSetContainsExactOrderedItems(emptySet.union(setLike), []); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + "next()", + ]); +} + +// Check input validation. +{ + let log = []; + + const nonCallable = {}; + let sizeValue = 0; + + let {proxy: keysIter} = LoggingProxy({ + next: nonCallable, + }, log); + + let {proxy: setLike, obj: setLikeObj} = LoggingProxy({ + size: { + valueOf() { + log.push("valueOf()"); + return sizeValue; + } + }, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + log.push("keys()"); + return keysIter; + }, + }, log); + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.union(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + "keys()", + "[[get]]", "next", + ]); + + // Change |keys| to return a non-object value. + setLikeObj.keys = () => 123; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.union(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |keys| to a non-callable value. + setLikeObj.keys = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.union(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + "[[get]]", "keys", + ]); + + // Change |has| to a non-callable value. + setLikeObj.has = nonCallable; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.union(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + "[[get]]", "has", + ]); + + // Change |size| to NaN. + sizeValue = NaN; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.union(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); + + // Change |size| to undefined. + sizeValue = undefined; + + log.length = 0; + assertThrowsInstanceOf(() => emptySet.union(setLike), TypeError); + + assertEqArray(log, [ + "[[get]]", "size", + "valueOf()", + ]); +} + +// Doesn't accept Array as an input. +assertThrowsInstanceOf(() => emptySet.union([]), TypeError); + +// Works with Set subclasses. +{ + class MySet extends Set {} + + let myEmptySet = new MySet; + + assertSetContainsExactOrderedItems(emptySet.union(myEmptySet), []); + assertSetContainsExactOrderedItems(myEmptySet.union(myEmptySet), []); + assertSetContainsExactOrderedItems(myEmptySet.union(emptySet), []); +} + +// Doesn't access any properties on the this-value. +{ + let log = []; + + let {proxy: setProto} = LoggingProxy(Set.prototype, log); + + let set = new Set([1, 2, 3]); + Object.setPrototypeOf(set, setProto); + + assertSetContainsExactOrderedItems(Set.prototype.union.call(set, emptySet), [1, 2, 3]); + + assertEqArray(log, []); +} + +// Throws a TypeError when the this-value isn't a Set object. +for (let thisValue of [ + null, undefined, true, "", {}, new Map, new Proxy(new Set, {}), +]) { + assertThrowsInstanceOf(() => Set.prototype.union.call(thisValue, emptySet), TypeError); +} + +// Doesn't return the original Set object. +{ + let set = new Set([1]); + assertEq(set.union(emptySet) !== set, true); + assertEq(set.union(new Set([2])) !== set, true); +} + +// Test insertion order +{ + let set = new Set([1, 2]); + + // Case 1: Input is empty. + assertSetContainsExactOrderedItems(set.union(new Set([])), [1, 2]); + + // Case 2: Input has fewer elements. + assertSetContainsExactOrderedItems(set.union(new Set([3])), [1, 2, 3]); + + // Case 3: Input has same number of elements. + assertSetContainsExactOrderedItems(set.union(new Set([3, 4])), [1, 2, 3, 4]); + + // Case 4: Input has more elements. + assertSetContainsExactOrderedItems(set.union(new Set([3, 4, 5])), [1, 2, 3, 4, 5]); +} + +// Test that the input set is copied after accessing the |next| property of the keys iterator. +{ + let set = new Set([1, 2, 3]); + + let setLike = { + size: 0, + has() { + throw new Error("Unexpected call to |has| method"); + }, + keys() { + return { + get next() { + // Clear the set when getting the |next| method. + set.clear(); + + // And then add a single new key. + set.add(4); + + return function() { + return {done: true}; + }; + } + }; + }, + }; + + // The result should consist of the single, newly added key. + assertSetContainsExactOrderedItems(set.union(setLike), [4]); +} + +// Tests which modify any built-ins should appear last, because modifications may disable +// optimised code paths. + +// Doesn't call the built-in |Set.prototype.{has, keys, size}| functions. +const SetPrototypeHas = Object.getOwnPropertyDescriptor(Set.prototype, "has"); +const SetPrototypeKeys = Object.getOwnPropertyDescriptor(Set.prototype, "keys"); +const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size"); + +delete Set.prototype.has; +delete Set.prototype.keys; +delete Set.prototype.size; + +try { + let set = new Set([1, 2, 3]); + let other = new SetLike([1, 2, 3]); + assertSetContainsExactOrderedItems(set.union(other), [1, 2, 3]); +} finally { + Object.defineProperty(Set.prototype, "has", SetPrototypeHas); + Object.defineProperty(Set.prototype, "keys", SetPrototypeKeys); + Object.defineProperty(Set.prototype, "size", SetPrototypeSize); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); |