From 60310b70bce0f76651785417c30c8a75e8c01316 Mon Sep 17 00:00:00 2001 From: Anthony Frehner Date: Wed, 15 Nov 2023 09:55:47 -0700 Subject: [PATCH] Add Set.prototype.union tests (#3816) * Beginning of adding Set.prototype.union tests * Fix description * Add tests for GetSetRecord Which also allows Set-like objects * Add title to test descriptions * Add test for ensuring values are appended * Add tests for properties of union Also: tests for RequireInternalSlot, Constructor, BuiltIns. Added a test to ensure that -0F is converted to +0F * Ensure Set.prototype.add is not called as part of .union * fix lint issues * Set subclassing and Symbol.species tests * Set.prototype.union tests for arrays and subclass methods * Add the Set-methods frontmatter feature flag * Add additional Set.prototype.union test for edge cases * Update test/built-ins/Set/GetSetRecord/keys-is-callable.js Co-authored-by: Kevin Gibbons * Use compareArray() for assertions * Remove class field syntax * Remove unused args * Update test/built-ins/Set/prototype/union/subclass-receiver-methods.js Co-authored-by: Jordan Harband * Return original 'add' * address comments * add one more mutation in the evil mutating iterator test * address further comments --------- Co-authored-by: Kevin Gibbons Co-authored-by: Jordan Harband Co-authored-by: Ms2ger --- features.txt | 1 + .../Set/prototype/union/add-not-called.js | 27 ++++++ .../prototype/union/allows-set-like-class.js | 34 ++++++++ .../prototype/union/allows-set-like-object.js | 30 +++++++ .../Set/prototype/union/appends-new-values.js | 30 +++++++ .../Set/prototype/union/array-throws.js | 17 ++++ .../built-ins/Set/prototype/union/builtins.js | 25 ++++++ .../Set/prototype/union/called-with-object.js | 66 +++++++++++++++ .../Set/prototype/union/combines-Map.js | 19 +++++ .../prototype/union/combines-empty-sets.js | 32 +++++++ .../Set/prototype/union/combines-itself.js | 15 ++++ .../Set/prototype/union/combines-same-sets.js | 16 ++++ .../Set/prototype/union/combines-sets.js | 16 ++++ .../prototype/union/converts-negative-zero.js | 35 ++++++++ .../Set/prototype/union/has-is-callable.js | 36 ++++++++ .../Set/prototype/union/keys-is-callable.js | 33 ++++++++ test/built-ins/Set/prototype/union/length.js | 18 ++++ test/built-ins/Set/prototype/union/name.js | 18 ++++ .../Set/prototype/union/not-a-constructor.js | 22 +++++ .../Set/prototype/union/receiver-not-set.js | 44 ++++++++++ .../prototype/union/require-internal-slot.js | 25 ++++++ .../Set/prototype/union/set-like-array.js | 24 ++++++ .../union/set-like-class-mutation.js | 47 +++++++++++ .../prototype/union/set-like-class-order.js | 83 +++++++++++++++++++ .../Set/prototype/union/size-is-a-number.js | 72 ++++++++++++++++ .../union/subclass-receiver-methods.js | 45 ++++++++++ .../union/subclass-symbol-species.js | 29 +++++++ .../built-ins/Set/prototype/union/subclass.js | 23 +++++ .../Set/prototype/union/union-properties.js | 20 +++++ 29 files changed, 902 insertions(+) create mode 100644 test/built-ins/Set/prototype/union/add-not-called.js create mode 100644 test/built-ins/Set/prototype/union/allows-set-like-class.js create mode 100644 test/built-ins/Set/prototype/union/allows-set-like-object.js create mode 100644 test/built-ins/Set/prototype/union/appends-new-values.js create mode 100644 test/built-ins/Set/prototype/union/array-throws.js create mode 100644 test/built-ins/Set/prototype/union/builtins.js create mode 100644 test/built-ins/Set/prototype/union/called-with-object.js create mode 100644 test/built-ins/Set/prototype/union/combines-Map.js create mode 100644 test/built-ins/Set/prototype/union/combines-empty-sets.js create mode 100644 test/built-ins/Set/prototype/union/combines-itself.js create mode 100644 test/built-ins/Set/prototype/union/combines-same-sets.js create mode 100644 test/built-ins/Set/prototype/union/combines-sets.js create mode 100644 test/built-ins/Set/prototype/union/converts-negative-zero.js create mode 100644 test/built-ins/Set/prototype/union/has-is-callable.js create mode 100644 test/built-ins/Set/prototype/union/keys-is-callable.js create mode 100644 test/built-ins/Set/prototype/union/length.js create mode 100644 test/built-ins/Set/prototype/union/name.js create mode 100644 test/built-ins/Set/prototype/union/not-a-constructor.js create mode 100644 test/built-ins/Set/prototype/union/receiver-not-set.js create mode 100644 test/built-ins/Set/prototype/union/require-internal-slot.js create mode 100644 test/built-ins/Set/prototype/union/set-like-array.js create mode 100644 test/built-ins/Set/prototype/union/set-like-class-mutation.js create mode 100644 test/built-ins/Set/prototype/union/set-like-class-order.js create mode 100644 test/built-ins/Set/prototype/union/size-is-a-number.js create mode 100644 test/built-ins/Set/prototype/union/subclass-receiver-methods.js create mode 100644 test/built-ins/Set/prototype/union/subclass-symbol-species.js create mode 100644 test/built-ins/Set/prototype/union/subclass.js create mode 100644 test/built-ins/Set/prototype/union/union-properties.js diff --git a/features.txt b/features.txt index 692cbf9bcc3..506ff6de792 100644 --- a/features.txt +++ b/features.txt @@ -210,6 +210,7 @@ regexp-named-groups regexp-unicode-property-escapes rest-parameters Set +Set-methods SharedArrayBuffer string-trimming String.fromCodePoint diff --git a/test/built-ins/Set/prototype/union/add-not-called.js b/test/built-ins/Set/prototype/union/add-not-called.js new file mode 100644 index 00000000000..01494e25a6b --- /dev/null +++ b/test/built-ins/Set/prototype/union/add-not-called.js @@ -0,0 +1,27 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union should not call Set.prototype.add +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const s2 = new Set([2, 3]); +const expected = [1, 2, 3]; + +const originalAdd = Set.prototype.add; +let count = 0; +Set.prototype.add = function (...rest) { + count++; + return originalAdd.apply(this, rest); +}; + +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); +assert.sameValue(count, 0, "Add is never called"); + +Set.prototype.add = originalAdd; diff --git a/test/built-ins/Set/prototype/union/allows-set-like-class.js b/test/built-ins/Set/prototype/union/allows-set-like-class.js new file mode 100644 index 00000000000..641b5925034 --- /dev/null +++ b/test/built-ins/Set/prototype/union/allows-set-like-class.js @@ -0,0 +1,34 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: GetSetRecord allows instances of Set-like classes +info: | + 1. If obj is not an Object, throw a TypeError exception. + 2. Let rawSize be ? Get(obj, "size"). + ... + 7. Let has be ? Get(obj, "has"). + ... + 9. Let keys be ? Get(obj, "keys"). +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const s2 = new class { + get size() { + return 2; + } + has() { + throw new Test262Error("Set.prototype.union should not invoke .has on its argument"); + } + * keys() { + yield 2; + yield 3; + } +}; +const expected = [1, 2, 3]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/allows-set-like-object.js b/test/built-ins/Set/prototype/union/allows-set-like-object.js new file mode 100644 index 00000000000..832ffaf884a --- /dev/null +++ b/test/built-ins/Set/prototype/union/allows-set-like-object.js @@ -0,0 +1,30 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: GetSetRecord allows Set-like objects +info: | + 1. If obj is not an Object, throw a TypeError exception. + 2. Let rawSize be ? Get(obj, "size"). + ... + 7. Let has be ? Get(obj, "has"). + ... + 9. Let keys be ? Get(obj, "keys"). +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const s2 = { + size: 2, + has: () => {}, + keys: function* keys() { + yield 2; + yield 3; + }, +}; +const expected = [1, 2, 3]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/appends-new-values.js b/test/built-ins/Set/prototype/union/appends-new-values.js new file mode 100644 index 00000000000..9d75ff84327 --- /dev/null +++ b/test/built-ins/Set/prototype/union/appends-new-values.js @@ -0,0 +1,30 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union appends new values to a copy of the original Set +info: | + 7.b.iii.1 Append nextValue to resultSetData. +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const s2 = new Set([-1, 0, 3]); +const expected = [1, 2, -1, 0, 3]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); + +const s3 = new Set([1, 2, -3]); +const s4 = new Set([-1, 0]); +const expected2 = [1, 2, -3, -1, 0]; +const combined2 = s3.union(s4); + +assert.compareArray([...combined2], expected2); +assert.sameValue( + combined2 instanceof Set, + true, + "The returned object is a Set" +); diff --git a/test/built-ins/Set/prototype/union/array-throws.js b/test/built-ins/Set/prototype/union/array-throws.js new file mode 100644 index 00000000000..80579b982f6 --- /dev/null +++ b/test/built-ins/Set/prototype/union/array-throws.js @@ -0,0 +1,17 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union doesn't work with arrays +features: [Set-methods] +---*/ + +const s1 = new Set([1, 2]); +const s2 = [3]; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "Throws an error when an array is used" +); diff --git a/test/built-ins/Set/prototype/union/builtins.js b/test/built-ins/Set/prototype/union/builtins.js new file mode 100644 index 00000000000..1ae1389b713 --- /dev/null +++ b/test/built-ins/Set/prototype/union/builtins.js @@ -0,0 +1,25 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Tests that Set.prototype.union meets the requirements for built-in objects +features: [Set-methods] +---*/ + +assert.sameValue( + Object.isExtensible(Set.prototype.union), + true, + "Built-in objects must be extensible." +); + +assert.sameValue( + Object.prototype.toString.call(Set.prototype.union), + "[object Function]", + "Object.prototype.toString" +); + +assert.sameValue( + Object.getPrototypeOf(Set.prototype.union), + Function.prototype, + "prototype" +); diff --git a/test/built-ins/Set/prototype/union/called-with-object.js b/test/built-ins/Set/prototype/union/called-with-object.js new file mode 100644 index 00000000000..569315b27a4 --- /dev/null +++ b/test/built-ins/Set/prototype/union/called-with-object.js @@ -0,0 +1,66 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-getsetrecord +description: GetSetRecord throws if obj is not an object +info: | + 1. If obj is not an Object, throw a TypeError exception. +features: [Set-methods] +---*/ + +let s1 = new Set([1]); +assert.throws( + TypeError, + function () { + s1.union(1); + }, + "number" +); + +assert.throws( + TypeError, + function () { + s1.union(""); + }, + "string" +); + +assert.throws( + TypeError, + function () { + s1.union(1n); + }, + "bigint" +); + +assert.throws( + TypeError, + function () { + s1.union(false); + }, + "boolean" +); + +assert.throws( + TypeError, + function () { + s1.union(undefined); + }, + "undefined" +); + +assert.throws( + TypeError, + function () { + s1.union(null); + }, + "null" +); + +assert.throws( + TypeError, + function () { + s1.union(Symbol("test")); + }, + "symbol" +); diff --git a/test/built-ins/Set/prototype/union/combines-Map.js b/test/built-ins/Set/prototype/union/combines-Map.js new file mode 100644 index 00000000000..d8d11a92032 --- /dev/null +++ b/test/built-ins/Set/prototype/union/combines-Map.js @@ -0,0 +1,19 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union combines with Map +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const m1 = new Map([ + [2, "two"], + [3, "three"], +]); +const expected = [1, 2, 3]; +const combined = s1.union(m1); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/combines-empty-sets.js b/test/built-ins/Set/prototype/union/combines-empty-sets.js new file mode 100644 index 00000000000..63a35821907 --- /dev/null +++ b/test/built-ins/Set/prototype/union/combines-empty-sets.js @@ -0,0 +1,32 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union can combine empty Sets +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([]); +const s2 = new Set([1, 2]); +let expected = [1, 2]; +let combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); + +const s3 = new Set([1, 2]); +const s4 = new Set([]); +expected = [1, 2]; +combined = s3.union(s4); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); + +const s5 = new Set([]); +const s6 = new Set([]); +expected = []; +combined = s5.union(s6); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/combines-itself.js b/test/built-ins/Set/prototype/union/combines-itself.js new file mode 100644 index 00000000000..78e0a9d18e9 --- /dev/null +++ b/test/built-ins/Set/prototype/union/combines-itself.js @@ -0,0 +1,15 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union is successful when called on itself +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const expected = [1, 2]; +const combined = s1.union(s1); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/combines-same-sets.js b/test/built-ins/Set/prototype/union/combines-same-sets.js new file mode 100644 index 00000000000..09bafaa9b0b --- /dev/null +++ b/test/built-ins/Set/prototype/union/combines-same-sets.js @@ -0,0 +1,16 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union can combine Sets that have the same content +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const s2 = new Set([1, 2]); +const expected = [1, 2]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/combines-sets.js b/test/built-ins/Set/prototype/union/combines-sets.js new file mode 100644 index 00000000000..5120410aa4d --- /dev/null +++ b/test/built-ins/Set/prototype/union/combines-sets.js @@ -0,0 +1,16 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union combines Sets +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const s2 = new Set([2, 3]); +const expected = [1, 2, 3]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/converts-negative-zero.js b/test/built-ins/Set/prototype/union/converts-negative-zero.js new file mode 100644 index 00000000000..e4abfb14964 --- /dev/null +++ b/test/built-ins/Set/prototype/union/converts-negative-zero.js @@ -0,0 +1,35 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union converts -0𝔽 to +0𝔽 +info: | + 7.b.ii. If nextValue is -0𝔽, set nextValue to +0𝔽. +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const setlikeWithMinusZero = { + size: 1, + has: function () { + throw new Test262Error("Set.prototype.union should not invoke .has on its argument"); + }, + keys: function () { + // we use an array here because the Set constructor would normalize away -0 + return [-0].values(); + }, +}; + +const s1 = new Set([1]); +let expected = [1, +0]; +let combined = s1.union(setlikeWithMinusZero); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); + +const s2 = new Set([+0]); +expected = [+0]; +combined = s2.union(setlikeWithMinusZero); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/has-is-callable.js b/test/built-ins/Set/prototype/union/has-is-callable.js new file mode 100644 index 00000000000..7cfefba0ba5 --- /dev/null +++ b/test/built-ins/Set/prototype/union/has-is-callable.js @@ -0,0 +1,36 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-getsetrecord +description: GetSetRecord throws an exception if the Set-like object's 'has' property is not callable +info: | + 7. Let has be ? Get(obj, "has"). + 8. If IsCallable(has) is false, throw a TypeError exception. +features: [Set-methods] +---*/ + +const s1 = new Set([1, 2]); +const s2 = { + size: 2, + has: undefined, + keys: function* keys() { + yield 2; + yield 3; + }, +}; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when has is undefined" +); + +s2.has = {}; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when has is not callable" +); diff --git a/test/built-ins/Set/prototype/union/keys-is-callable.js b/test/built-ins/Set/prototype/union/keys-is-callable.js new file mode 100644 index 00000000000..7adb2ae84a5 --- /dev/null +++ b/test/built-ins/Set/prototype/union/keys-is-callable.js @@ -0,0 +1,33 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-getsetrecord +description: GetSetRecord throws an exception if the Set-like object's 'keys' property is not callable +info: | + 9. Let keys be ? Get(obj, "keys"). + 10. If IsCallable(keys) is false, throw a TypeError exception. +features: [Set-methods] +---*/ + +const s1 = new Set([1, 2]); +const s2 = { + size: 2, + has: () => {}, + keys: undefined, +}; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when keys is undefined" +); + +s2.keys = {}; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when keys is not callable" +); diff --git a/test/built-ins/Set/prototype/union/length.js b/test/built-ins/Set/prototype/union/length.js new file mode 100644 index 00000000000..f5c5fb0f0b9 --- /dev/null +++ b/test/built-ins/Set/prototype/union/length.js @@ -0,0 +1,18 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union length property +info: | + Set.prototype.union ( other )] +includes: [propertyHelper.js] +features: [Set-methods] +---*/ +assert.sameValue(typeof Set.prototype.union, "function"); + +verifyProperty(Set.prototype.union, "length", { + enumerable: false, + writable: false, + configurable: true, + value: 1, +}); diff --git a/test/built-ins/Set/prototype/union/name.js b/test/built-ins/Set/prototype/union/name.js new file mode 100644 index 00000000000..07cd8f667e9 --- /dev/null +++ b/test/built-ins/Set/prototype/union/name.js @@ -0,0 +1,18 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union name property +info: | + Set.prototype.union ( other )] +includes: [propertyHelper.js] +features: [Set-methods] +---*/ +assert.sameValue(typeof Set.prototype.union, "function"); + +verifyProperty(Set.prototype.union, "name", { + enumerable: false, + writable: false, + configurable: true, + value: "union", +}); diff --git a/test/built-ins/Set/prototype/union/not-a-constructor.js b/test/built-ins/Set/prototype/union/not-a-constructor.js new file mode 100644 index 00000000000..e8a9bb5a8fc --- /dev/null +++ b/test/built-ins/Set/prototype/union/not-a-constructor.js @@ -0,0 +1,22 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union does not implement [[Construct]], is not new-able +includes: [isConstructor.js] +features: [Reflect.construct, Set-methods] +---*/ + +assert.sameValue( + isConstructor(Set.prototype.union), + false, + "isConstructor(Set.prototype.union) must return false" +); + +assert.throws( + TypeError, + () => { + new Set.prototype.union(); + }, + "`new Set.prototype.union()` throws TypeError" +); diff --git a/test/built-ins/Set/prototype/union/receiver-not-set.js b/test/built-ins/Set/prototype/union/receiver-not-set.js new file mode 100644 index 00000000000..87508cde473 --- /dev/null +++ b/test/built-ins/Set/prototype/union/receiver-not-set.js @@ -0,0 +1,44 @@ +// Copyright (C) 2023 Kevin Gibbons, Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union throws when receiver is not a Set +features: [Set-methods] +---*/ + +class MySetLike { + constructor() { + this.size = 2; + this.has = () => {}; + this.keys = function* keys() { + yield 2; + yield 3; + }; + } +} + +const s1 = new MySetLike(); +const s2 = new Set(); +assert.throws( + TypeError, + () => { + Set.prototype.union.call(s1, s2); + }, + "Set-like class" +); + +const s3 = { + size: 2, + has: () => {}, + keys: function* keys() { + yield 2; + yield 3; + }, +}; +assert.throws( + TypeError, + () => { + Set.prototype.union.call(s3, s2); + }, + "Set-like object" +); diff --git a/test/built-ins/Set/prototype/union/require-internal-slot.js b/test/built-ins/Set/prototype/union/require-internal-slot.js new file mode 100644 index 00000000000..66c1fc87054 --- /dev/null +++ b/test/built-ins/Set/prototype/union/require-internal-slot.js @@ -0,0 +1,25 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union RequireInternalSlot +info: | + 2. Perform ? RequireInternalSlot(O, [[SetData]]) +features: [Set-methods] +---*/ + +const union = Set.prototype.union; + +assert.sameValue(typeof union, "function"); + +assert.throws(TypeError, () => union.call(undefined), "undefined"); +assert.throws(TypeError, () => union.call(null), "null"); +assert.throws(TypeError, () => union.call(true), "true"); +assert.throws(TypeError, () => union.call(""), "empty string"); +assert.throws(TypeError, () => union.call(Symbol()), "symbol"); +assert.throws(TypeError, () => union.call(1), "1"); +assert.throws(TypeError, () => union.call(1n), "1n"); +assert.throws(TypeError, () => union.call({}), "plain object"); +assert.throws(TypeError, () => union.call([]), "array"); +assert.throws(TypeError, () => union.call(new Map()), "map"); +assert.throws(TypeError, () => union.call(Set.prototype), "Set.prototype"); diff --git a/test/built-ins/Set/prototype/union/set-like-array.js b/test/built-ins/Set/prototype/union/set-like-array.js new file mode 100644 index 00000000000..3d263d75bb0 --- /dev/null +++ b/test/built-ins/Set/prototype/union/set-like-array.js @@ -0,0 +1,24 @@ +// Copyright (C) 2023 Kevin Gibbons, Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union consumes a set-like array as a set-like, not an array +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const s1 = new Set([1, 2]); +const s2 = [5, 6]; +s2.size = 3; +s2.has = function () { + throw new Test262Error("Set.prototype.union should not invoke .has on its argument"); +}; +s2.keys = function () { + return [2, 3, 4].values(); +}; + +const expected = [1, 2, 3, 4]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); diff --git a/test/built-ins/Set/prototype/union/set-like-class-mutation.js b/test/built-ins/Set/prototype/union/set-like-class-mutation.js new file mode 100644 index 00000000000..b6a4ab292e1 --- /dev/null +++ b/test/built-ins/Set/prototype/union/set-like-class-mutation.js @@ -0,0 +1,47 @@ +// Copyright (C) 2023 Kevin Gibbons, Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union maintains values even when a custom Set-like class mutates the receiver +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const baseSet = new Set(["a", "b", "c", "d", "e"]); + +function mutatingIterator() { + let index = 0; + let values = ["x", "y"]; + return { + next() { + baseSet.delete("b"); + baseSet.delete("c"); + baseSet.add("b"); + baseSet.add("d"); + return { + done: index >= 2, + value: values[index++], + }; + }, + }; +} + +const evilSetLike = { + size: 2, + get has() { + baseSet.add("q"); + return function () { + throw new Test262Error("Set.prototype.union should not invoke .has on its argument"); + }; + }, + keys() { + return mutatingIterator(); + }, +}; + +const combined = baseSet.union(evilSetLike); +const expectedCombined = ["a", "b", "c", "d", "e", "q", "x", "y"]; +assert.compareArray([...combined], expectedCombined); + +const expectedNewBase = ["a", "d", "e", "q", "b"]; +assert.compareArray([...baseSet], expectedNewBase); diff --git a/test/built-ins/Set/prototype/union/set-like-class-order.js b/test/built-ins/Set/prototype/union/set-like-class-order.js new file mode 100644 index 00000000000..2729c13553b --- /dev/null +++ b/test/built-ins/Set/prototype/union/set-like-class-order.js @@ -0,0 +1,83 @@ +// Copyright (C) 2023 Kevin Gibbons, Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union calls a Set-like class's methods in order +features: [Set-methods] +includes: [compareArray.js] +---*/ + +const observedOrder = []; +const expectedOrder = [ + "getting size", + "ToNumber(size)", + "getting has", + "getting keys", + "calling keys", + "getting next", + // first iteration, has value + "calling next", + "getting done", + "getting value", + // second iteration, has value + "calling next", + "getting done", + "getting value", + // third iteration, no value; ends + "calling next", + "getting done", +]; + +function observableIterator() { + let values = ["a", "b"]; + let index = 0; + return { + get next() { + observedOrder.push("getting next"); + return function () { + observedOrder.push("calling next"); + return { + get done() { + observedOrder.push("getting done"); + return index >= values.length; + }, + get value() { + observedOrder.push("getting value"); + return values[index++]; + }, + }; + }; + }, + }; +} + +class MySetLike { + get size() { + observedOrder.push("getting size"); + return { + valueOf: function () { + observedOrder.push("ToNumber(size)"); + return 2; + }, + }; + } + get has() { + observedOrder.push("getting has"); + return function () { + throw new Test262Error("Set.prototype.union should not invoke .has on its argument"); + }; + } + get keys() { + observedOrder.push("getting keys"); + return function () { + observedOrder.push("calling keys"); + return observableIterator(); + }; + } +} + +const s1 = new Set([1, 2]); +const s2 = new MySetLike(); +const combined = s1.union(s2); + +assert.compareArray(observedOrder, expectedOrder); diff --git a/test/built-ins/Set/prototype/union/size-is-a-number.js b/test/built-ins/Set/prototype/union/size-is-a-number.js new file mode 100644 index 00000000000..e25200e8551 --- /dev/null +++ b/test/built-ins/Set/prototype/union/size-is-a-number.js @@ -0,0 +1,72 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-getsetrecord +description: GetSetRecord throws an exception if the Set-like object has a size that is coerced to NaN +info: | + 2. Let rawSize be ? Get(obj, "size"). + 3. Let numSize be ? ToNumber(rawSize). + 4. NOTE: If rawSize is undefined, then numSize will be NaN. + 5. If numSize is NaN, throw a TypeError exception. +features: [Set-methods] +---*/ + +const s1 = new Set([1, 2]); +const s2 = { + size: undefined, + has: () => {}, + keys: function* keys() { + yield 2; + yield 3; + }, +}; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when size is undefined" +); + +s2.size = NaN; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when size is NaN" +); + +let coercionCalls = 0; +s2.size = { + valueOf: function() { + ++coercionCalls; + return NaN; + }, +}; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when size coerces to NaN" +); +assert.sameValue(coercionCalls, 1, "GetSetRecord coerces size"); + +s2.size = 0n; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when size is a BigInt" +); + +s2.size = "string"; +assert.throws( + TypeError, + function () { + s1.union(s2); + }, + "GetSetRecord throws an error when size is a non-numeric string" +); diff --git a/test/built-ins/Set/prototype/union/subclass-receiver-methods.js b/test/built-ins/Set/prototype/union/subclass-receiver-methods.js new file mode 100644 index 00000000000..1d47104a4d1 --- /dev/null +++ b/test/built-ins/Set/prototype/union/subclass-receiver-methods.js @@ -0,0 +1,45 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union works on subclasses of Set, but never calls the receiver's size/has/keys methods +features: [Set-methods] +includes: [compareArray.js] +---*/ + +let sizeCount = 0; +let hasCount = 0; +let keysCount = 0; + +class MySet extends Set { + size(...rest) { + sizeCount++; + return super.size(...rest); + } + + has(...rest) { + hasCount++; + return super.has(...rest); + } + + keys(...rest) { + keysCount++; + return super.keys(...rest); + } +} + +const s1 = new MySet([1, 2]); +const s2 = new Set([2, 3]); +const expected = [1, 2, 3]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); +assert.sameValue( + combined instanceof MySet, + false, + "The returned object is a Set, not a subclass" +); +assert.sameValue(sizeCount, 0, "size should not be called on the receiver"); +assert.sameValue(hasCount, 0, "has should not be called on the receiver"); +assert.sameValue(keysCount, 0, "keys should not be called on the receiver"); diff --git a/test/built-ins/Set/prototype/union/subclass-symbol-species.js b/test/built-ins/Set/prototype/union/subclass-symbol-species.js new file mode 100644 index 00000000000..e625f03a170 --- /dev/null +++ b/test/built-ins/Set/prototype/union/subclass-symbol-species.js @@ -0,0 +1,29 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union works on subclasses of Set, but returns an instance of Set even when Symbol.species is overridden. +features: [Set-methods] +includes: [compareArray.js] +---*/ +var count = 0; +class MySet extends Set { + static get [Symbol.species]() { + count++; + return Set; + } +} + +const s1 = new MySet([1, 2]); +const s2 = new Set([2, 3]); +const expected = [1, 2, 3]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(count, 0, "Symbol.species is never called"); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); +assert.sameValue( + combined instanceof MySet, + false, + "The returned object is a Set, not a subclass" +); diff --git a/test/built-ins/Set/prototype/union/subclass.js b/test/built-ins/Set/prototype/union/subclass.js new file mode 100644 index 00000000000..804e6f8aec9 --- /dev/null +++ b/test/built-ins/Set/prototype/union/subclass.js @@ -0,0 +1,23 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union works on subclasses of Set, but returns an instance of Set +features: [Set-methods] +includes: [compareArray.js] +---*/ + +class MySet extends Set {} + +const s1 = new MySet([1, 2]); +const s2 = new Set([2, 3]); +const expected = [1, 2, 3]; +const combined = s1.union(s2); + +assert.compareArray([...combined], expected); +assert.sameValue(combined instanceof Set, true, "The returned object is a Set"); +assert.sameValue( + combined instanceof MySet, + false, + "The returned object is a Set, not a subclass" +); diff --git a/test/built-ins/Set/prototype/union/union-properties.js b/test/built-ins/Set/prototype/union/union-properties.js new file mode 100644 index 00000000000..33c10b9ed9e --- /dev/null +++ b/test/built-ins/Set/prototype/union/union-properties.js @@ -0,0 +1,20 @@ +// Copyright (C) 2023 Anthony Frehner. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-set.prototype.union +description: Set.prototype.union properties +includes: [propertyHelper.js] +features: [Set-methods] +---*/ + +assert.sameValue( + typeof Set.prototype.union, + "function", + "`typeof Set.prototype.union` is `'function'`" +); + +verifyProperty(Set.prototype, "union", { + enumerable: false, + writable: true, + configurable: true, +});