diff --git a/features.txt b/features.txt index 065eb259c80..5ed5a322e39 100644 --- a/features.txt +++ b/features.txt @@ -92,6 +92,10 @@ nonextensible-applies-to-private # https://github.com/tc39/proposal-joint-iteration joint-iteration +# Iterator Join +# https://github.com/tc39/proposal-iterator-join +Iterator.prototype.join + ## Standard language features # # Language features that have been included in a published version of the diff --git a/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js b/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js new file mode 100644 index 00000000000..33b8e84519a --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js @@ -0,0 +1,36 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join closes its receiver if coercing a value from the iterator throws. +features: [Iterator.prototype.join] +---*/ + +var throwy = { + toString: function () { + throw new Test262Error(); + }, +}; + +var calledNextCount = 0; +var calledReturn = false; +var it = { + next: function () { + ++calledNextCount; + if (calledNextCount > 1) { + return { done: true, value: undefined }; + } + return { done: false, value: throwy }; + }, + return: function () { + calledReturn = true; + }, +}; + +assert.throws(Test262Error, function () { + Iterator.prototype.join.call(it); +}); + +assert.sameValue(calledNextCount, 1); +assert(calledReturn); diff --git a/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js b/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js new file mode 100644 index 00000000000..d08025d885d --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js @@ -0,0 +1,33 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join closes its receiver if coercing the separator throws. +features: [Iterator.prototype.join] +---*/ + +var throwy = { + toString: function () { + throw new Test262Error(); + }, +}; + +var gotNext = false; +var calledReturn = false; +var it = { + get next() { + // we use a variable instead of simply throwing because throwing is expected in this test + gotNext = true; + }, + return: function () { + calledReturn = true; + }, +}; + +assert.throws(Test262Error, function () { + Iterator.prototype.join.call(it, throwy); +}); + +assert.sameValue(gotNext, false); +assert(calledReturn); diff --git a/test/built-ins/Iterator/prototype/join/contents-nullish.js b/test/built-ins/Iterator/prototype/join/contents-nullish.js new file mode 100644 index 00000000000..79fbda77bbb --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/contents-nullish.js @@ -0,0 +1,13 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join formats nullish iterator contents as an empty string. +features: [Iterator.prototype.join] +---*/ + +assert.sameValue( + ['one', null, 'two', undefined, 'three'].values().join(), + 'one,,two,,three' +); diff --git a/test/built-ins/Iterator/prototype/join/contents-tostring.js b/test/built-ins/Iterator/prototype/join/contents-tostring.js new file mode 100644 index 00000000000..c721124473b --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/contents-tostring.js @@ -0,0 +1,22 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join coerces non-nullish iterator contents to string. +features: [Iterator.prototype.join] +---*/ + +var called = false; +var coercible = { + toString: function () { + if (called) { + throw new Test262Error('toString should be called exactly once'); + } + called = true; + return 'value'; + }, +}; + +assert.sameValue([coercible, 0, true].values().join(), 'value,0,true'); +assert(called); diff --git a/test/built-ins/Iterator/prototype/join/descriptor.js b/test/built-ins/Iterator/prototype/join/descriptor.js new file mode 100644 index 00000000000..48c95f1e58e --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/descriptor.js @@ -0,0 +1,15 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join has default data property attributes. +includes: [propertyHelper.js] +features: [Iterator.prototype.join] +---*/ + +verifyProperty(Iterator.prototype, 'join', { + enumerable: false, + writable: true, + configurable: true +}); diff --git a/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js new file mode 100644 index 00000000000..7f487ac474d --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join does not close its receiver if the iterator itself throws. +features: [Iterator.prototype.join] +---*/ + +var gotReturn = false; +var it = { + next: function () { + throw new Test262Error(); + }, + get return() { + gotReturn = true; + }, +}; + +assert.throws(Test262Error, function () { + Iterator.prototype.join.call(it); +}); + +assert.sameValue(gotReturn, false); diff --git a/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js new file mode 100644 index 00000000000..cafadddfc7b --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js @@ -0,0 +1,29 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join does not close its receiver if the iterator is exhausted. +features: [Iterator.prototype.join] +---*/ + +var calledNextCount = 0; +var gotReturn = false; +var it = { + next: function () { + ++calledNextCount; + if (calledNextCount > 2) { + return { done: true, value: undefined }; + } + return { done: false, value: 'ES' }; + }, + get return() { + gotReturn = true; + }, +}; + +assert.sameValue(Iterator.prototype.join.call(it), 'ES,ES'); + +assert.sameValue(calledNextCount, 3); + +assert.sameValue(gotReturn, false); diff --git a/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js new file mode 100644 index 00000000000..88e1f1b669e --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join does not close its receiver if the iterator itself throws. +features: [Iterator.prototype.join] +---*/ + +var gotReturn = false; +var it = { + next: function () { + return null; + }, + get return() { + gotReturn = true; + }, +}; + +assert.throws(TypeError, function () { + Iterator.prototype.join.call(it); +}); + +assert.sameValue(gotReturn, false); diff --git a/test/built-ins/Iterator/prototype/join/length.js b/test/built-ins/Iterator/prototype/join/length.js new file mode 100644 index 00000000000..8d32126b02b --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/length.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join.length is 1. +includes: [propertyHelper.js] +features: [Iterator.prototype.join] +---*/ + +verifyProperty(Iterator.prototype.join, 'length', { + value: 1, + enumerable: false, + writable: false, + configurable: true +}); diff --git a/test/built-ins/Iterator/prototype/join/name.js b/test/built-ins/Iterator/prototype/join/name.js new file mode 100644 index 00000000000..1e94ebd0c91 --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/name.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join.name is "join". +includes: [propertyHelper.js] +features: [Iterator.prototype.join] +---*/ + +verifyProperty(Iterator.prototype.join, 'name', { + value: 'join', + enumerable: false, + writable: false, + configurable: true +}); diff --git a/test/built-ins/Iterator/prototype/join/not-a-constructor.js b/test/built-ins/Iterator/prototype/join/not-a-constructor.js new file mode 100644 index 00000000000..0929c2b9585 --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/not-a-constructor.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join is not a constructor +includes: [isConstructor.js] +features: [Iterator.prototype.join, Reflect.construct] +---*/ + +assert(!isConstructor(Iterator.prototype.join), "Iterator.prototype.join should not be a constructor"); + +assert.throws(TypeError, function() { + var iterator = [].values(); + new iterator.join(); +}); diff --git a/test/built-ins/Iterator/prototype/join/receiver-not-object.js b/test/built-ins/Iterator/prototype/join/receiver-not-object.js new file mode 100644 index 00000000000..15b543299b6 --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/receiver-not-object.js @@ -0,0 +1,38 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join throws if the receiver is not an object. +features: [Iterator.prototype.join] +---*/ + +var it = [].values(); + +assert.throws(TypeError, function () { + it.join.call(undefined); +}); + +assert.throws(TypeError, function () { + it.join.call(null); +}); + +assert.throws(TypeError, function () { + it.join.call(false); +}); + +assert.throws(TypeError, function () { + it.join.call(0); +}); + +assert.throws(TypeError, function () { + it.join.call(0n); +}); + +assert.throws(TypeError, function () { + it.join.call(""); +}); + +assert.throws(TypeError, function () { + it.join.call(Symbol()); +}); diff --git a/test/built-ins/Iterator/prototype/join/results-empty-separator.js b/test/built-ins/Iterator/prototype/join/results-empty-separator.js new file mode 100644 index 00000000000..ed1ea2adab5 --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/results-empty-separator.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join allows empty string as separator. +features: [Iterator.prototype.join] +---*/ + +assert.sameValue([].values().join(''), ''); + +assert.sameValue(['one'].values().join(''), 'one'); + +assert.sameValue(['one', 'two'].values().join(''), 'onetwo'); + +assert.sameValue(['one', 'two', 'three'].values().join(''), 'onetwothree'); diff --git a/test/built-ins/Iterator/prototype/join/results-no-separator.js b/test/built-ins/Iterator/prototype/join/results-no-separator.js new file mode 100644 index 00000000000..231bbba8f5f --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/results-no-separator.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join joins using a comma if no separator is passed. +features: [Iterator.prototype.join] +---*/ + +assert.sameValue([].values().join(), ''); + +assert.sameValue(['one'].values().join(), 'one'); + +assert.sameValue(['one', 'two'].values().join(), 'one,two'); + +assert.sameValue(['one', 'two', 'three'].values().join(), 'one,two,three'); diff --git a/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js b/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js new file mode 100644 index 00000000000..3ed3424ad5c --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join joins using the passed separator. +features: [Iterator.prototype.join] +---*/ + +assert.sameValue([].values().join('&&'), ''); + +assert.sameValue(['one'].values().join('&&'), 'one'); + +assert.sameValue(['one', 'two'].values().join('&&'), 'one&&two'); + +assert.sameValue(['one', 'two', 'three'].values().join('&&'), 'one&&two&&three'); diff --git a/test/built-ins/Iterator/prototype/join/separator-tostring.js b/test/built-ins/Iterator/prototype/join/separator-tostring.js new file mode 100644 index 00000000000..8c750ae3c08 --- /dev/null +++ b/test/built-ins/Iterator/prototype/join/separator-tostring.js @@ -0,0 +1,26 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join coerces the passed separator to a string. +features: [Iterator.prototype.join] +---*/ + +var called = false; +var coercible = { + toString: function () { + if (called) { + throw new Test262Error('toString should be called exactly once'); + } + called = true; + return '&&'; + }, +}; + +assert.sameValue(['one', 'two', 'three'].values().join(coercible), 'one&&two&&three'); +assert(called); + +assert.sameValue(['one', 'two', 'three'].values().join(undefined), 'one,two,three'); + +assert.sameValue(['one', 'two', 'three'].values().join(null), 'onenulltwonullthree');