diff --git a/harness/iteratorZipUtils.js b/harness/iteratorZipUtils.js new file mode 100644 index 0000000000..05e6c0f04d --- /dev/null +++ b/harness/iteratorZipUtils.js @@ -0,0 +1,223 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: | + Utility functions for testing Iterator.prototype.zip and Iterator.prototype.zipKeyed. Requires inclusion of propertyHelper.js. +defines: + - forEachSequenceCombination + - forEachSequenceCombinationKeyed + - assertZipped + - assertZippedKeyed + - assertIteratorResult + - assertIsPackedArray +---*/ + +// Assert |result| is an object created by CreateIteratorResultObject. +function assertIteratorResult(result, value, done, label) { + assert.sameValue( + Object.getPrototypeOf(result), + Object.prototype, + label + ": [[Prototype]] of iterator result is Object.prototype" + ); + + assert(Object.isExtensible(result), label + ": iterator result is extensible"); + + var ownKeys = Reflect.ownKeys(result); + assert.compareArray(ownKeys, ["value", "done"], label + ": iterator result properties"); + + verifyProperty(result, "value", { + value: value, + writable: true, + enumerable: true, + configurable: true, + }); + + verifyProperty(result, "done", { + value: done, + writable: true, + enumerable: true, + configurable: true, + }); +} + +// Assert |array| is a packed array with default property attributes. +function assertIsPackedArray(array, label) { + assert(Array.isArray(array), label + ": array is an array exotic object"); + + assert.sameValue( + Object.getPrototypeOf(array), + Array.prototype, + label + ": [[Prototype]] of array is Array.prototype" + ); + + assert(Object.isExtensible(array), label + ": array is extensible"); + + // Ensure "length" property has its default property attributes. + verifyProperty(array, "length", { + writable: true, + enumerable: false, + configurable: false, + }); + + // Ensure no holes and all elements have the default property attributes. + for (var i = 0; i < array.length; i++) { + verifyProperty(array, i, { + writable: true, + enumerable: true, + configurable: true, + }); + } +} + +// Assert |array| is an extensible null-prototype object with default property attributes. +function _assertIsNullProtoMutableObject(object, label) { + assert.sameValue( + Object.getPrototypeOf(object), + null, + label + ": [[Prototype]] of object is null" + ); + + assert(Object.isExtensible(object), label + ": object is extensible"); + + // Ensure all properties have the default property attributes. + var keys = Object.getOwnPropertyNames(object); + for (var i = 0; i < keys.length; i++) { + verifyProperty(object, keys[i], { + writable: true, + enumerable: true, + configurable: true, + }); + } +} + +// Assert that the `zipped` iterator results as for Iterator.zip for the first `count` elements. +// Assumes inputs is an array of arrays of length >= count. +// Advances `zipped` by `count` steps. +function assertZipped(zipped, inputs, count, label) { + // Last returned elements array. + var last = null; + + for (var i = 0; i < count; i++) { + var itemLabel = label + ", step " + i; + + var result = zipped.next(); + var value = result.value; + + // Test IteratorResult structure. + assertIteratorResult(result, value, false, itemLabel); + + // Ensure value is a new array. + assert.notSameValue(value, last, itemLabel + ": returns a new array"); + last = value; + + // Ensure all array elements have the expected value. + var expected = inputs.map(function (array) { + return array[i]; + }); + assert.compareArray(value, expected, itemLabel + ": values"); + + // Ensure value is a packed array with default data properties. + // + // This operation is destructive, so it has to happen last. + assertIsPackedArray(value, itemLabel); + } +} + +// Assert that the `zipped` iterator results as for Iterator.zipKeyed for the first `count` elements. +// Assumes inputs is an object whose values are arrays of length >= count. +// Advances `zipped` by `count` steps. +function assertZippedKeyed(zipped, inputs, count, label) { + // Last returned elements array. + var last = null; + + var expectedKeys = Object.keys(inputs); + + for (var i = 0; i < count; i++) { + var itemLabel = label + ", step " + i; + + var result = zipped.next(); + var value = result.value; + + // Test IteratorResult structure. + assertIteratorResult(result, value, false, itemLabel); + + // Ensure resulting object is a new object. + assert.notSameValue(value, last, itemLabel + ": returns a new object"); + last = value; + + // Ensure resulting object has the expected keys and values. + assert.compareArray(Reflect.ownKeys(value), expectedKeys, itemLabel + ": result object keys"); + + var expectedValues = Object.values(inputs).map(function (array) { + return array[i]; + }); + assert.compareArray(Object.values(value), expectedValues, itemLabel + ": result object values"); + + // Ensure resulting object is a null-prototype mutable object with default data properties. + // + // This operation is destructive, so it has to happen last. + _assertIsNullProtoMutableObject(value, itemLabel); + } +} + +function forEachSequenceCombinationKeyed(callback) { + return forEachSequenceCombination(function(inputs, inputsLabel, min, max) { + var object = {}; + for (var i = 0; i < inputs.length; ++i) { + object["prop_" + i] = inputs[i]; + } + inputsLabel = "inputs = " + JSON.stringify(object); + callback(object, inputsLabel, min, max); + }); +} + +function forEachSequenceCombination(callback) { + function test(inputs) { + if (inputs.length === 0) { + callback(inputs, "inputs = []", 0, 0); + return; + } + + var lengths = inputs.map(function(array) { + return array.length; + }); + + var min = Math.min.apply(null, lengths); + var max = Math.max.apply(null, lengths); + + var inputsLabel = "inputs = " + JSON.stringify(inputs); + + callback(inputs, inputsLabel, min, max); + } + + // Yield all prefixes of the string |s|. + function* prefixes(s) { + for (var i = 0; i <= s.length; ++i) { + yield s.slice(0, i); + } + } + + // Zip an empty iterable. + test([]); + + // Zip a single iterator. + for (var prefix of prefixes("abcd")) { + test([prefix.split("")]); + } + + // Zip two iterators. + for (var prefix1 of prefixes("abcd")) { + for (var prefix2 of prefixes("efgh")) { + test([prefix1.split(""), prefix2.split("")]); + } + } + + // Zip three iterators. + for (var prefix1 of prefixes("abcd")) { + for (var prefix2 of prefixes("efgh")) { + for (var prefix3 of prefixes("ijkl")) { + test([prefix1.split(""), prefix2.split(""), prefix3.split("")]); + } + } + } +} diff --git a/test/built-ins/Iterator/zip/basic-longest.js b/test/built-ins/Iterator/zip/basic-longest.js new file mode 100644 index 0000000000..c74895e8e2 --- /dev/null +++ b/test/built-ins/Iterator/zip/basic-longest.js @@ -0,0 +1,85 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Basic Iterator.zip test with "longest" mode. +includes: [compareArray.js, propertyHelper.js, iteratorZipUtils.js] +features: [joint-iteration] +---*/ + +function testSequence(inputs, inputsLabel, minLength, maxLength) { + function test(options, optionsLabel, getPaddingForInput) { + var label = optionsLabel + ", " + inputsLabel; + var it = Iterator.zip(inputs, options); + assertZipped(it, inputs, minLength, label); + + for (var i = minLength; i < maxLength; i++) { + var itemLabel = label + ", step " + i; + + var result = it.next(); + var value = result.value; + + // Test IteratorResult structure. + assertIteratorResult(result, value, false, itemLabel); + + var expected = inputs.map(function (input, j) { + return i < input.length ? input[i] : getPaddingForInput(j); + }); + assert.compareArray(value, expected, itemLabel + ": values"); + } + assertIteratorResult(it.next(), undefined, true, label + ": after completion"); + } + + test( + { mode: "longest" }, + "options = { mode: 'longest' }", + function () { + return undefined; + }, + ); + + test( + { mode: "longest", padding: [] }, + "options = { mode: 'longest', padding: [] }", + function () { + return undefined; + }, + ); + + test( + { mode: "longest", padding: ["pad"] }, + "options = { mode: 'longest', padding: ['pad'] }", + function (idx) { + return idx === 0 ? "pad" : undefined; + }, + ); + + test( + { mode: "longest", padding: Array(inputs.length).fill("pad") }, + "options = { mode: 'longest', padding: ['pad', 'pad', ..., 'pad'] }", + function (idx) { + return "pad"; + }, + ); + + // Yield an infinite amount of numbers. + var numbers = { + *[Symbol.iterator]() { + var i = 0; + while (true) { + yield 100 + i++; + } + } + }; + test( + { mode: "longest", padding: numbers }, + "options = { mode: 'longest', padding: numbers }", + function (idx) { + return 100 + idx; + }, + ); +} + +forEachSequenceCombination(testSequence); diff --git a/test/built-ins/Iterator/zip/basic-shortest.js b/test/built-ins/Iterator/zip/basic-shortest.js new file mode 100644 index 0000000000..8487c0fe7e --- /dev/null +++ b/test/built-ins/Iterator/zip/basic-shortest.js @@ -0,0 +1,29 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Basic Iterator.zip test with "shortest" mode. +includes: [compareArray.js, propertyHelper.js, iteratorZipUtils.js] +features: [joint-iteration] +---*/ + +function testSequence(inputs, inputsLabel, minLength, maxLength) { + function test(options, optionsLabel) { + var label = optionsLabel + ", " + inputsLabel; + var it = Iterator.zip(inputs, options); + assertZipped(it, inputs, minLength, label); + + // Iterator is closed after `minLength` items. + assertIteratorResult(it.next(), undefined, true, label + ": after completion"); + } + + test(undefined, "options = undefined"); + + test({}, "options = {}"); + + test({ mode: "shortest" }, "options = { mode: 'shortest' }"); +} + +forEachSequenceCombination(testSequence); diff --git a/test/built-ins/Iterator/zip/basic-strict.js b/test/built-ins/Iterator/zip/basic-strict.js new file mode 100644 index 0000000000..d41ec7e56b --- /dev/null +++ b/test/built-ins/Iterator/zip/basic-strict.js @@ -0,0 +1,30 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Basic Iterator.zip test with "strict" mode. +includes: [compareArray.js, propertyHelper.js, iteratorZipUtils.js] +features: [joint-iteration] +---*/ + +function testSequence(inputs, inputsLabel, minLength, maxLength) { + function test(options, optionsLabel) { + var label = optionsLabel + ", " + inputsLabel; + var it = Iterator.zip(inputs, options); + assertZipped(it, inputs, minLength, label); + + if (minLength === maxLength) { + assertIteratorResult(it.next(), undefined, true, label + ": after completion"); + } else { + assert.throws(TypeError, function() { + it.next(); + }, label + " should throw after " + minLength + " items."); + } + } + + test({ mode: "strict" }, "options = { mode: 'strict' }"); +} + +forEachSequenceCombination(testSequence); diff --git a/test/built-ins/Iterator/zip/basic.js b/test/built-ins/Iterator/zip/basic.js deleted file mode 100644 index a11102ccbe..0000000000 --- a/test/built-ins/Iterator/zip/basic.js +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (C) 2025 André Bargull. All rights reserved. -// This code is governed by the BSD license found in the LICENSE file. - -/*--- -esid: sec-iterator.zip -description: > - Basic Iterator.zip test using all three possible modes. -includes: [compareArray.js, propertyHelper.js] -features: [joint-iteration] ----*/ - -// Assert |result| is an object created by CreateIteratorResultObject. -function assertIteratorResult(result, value, done) { - assert.sameValue( - Object.getPrototypeOf(result), - Object.prototype, - "[[Prototype]] of iterator result is Object.prototype" - ); - - assert(Object.isExtensible(result), "iterator result is extensible"); - - var ownKeys = Reflect.ownKeys(result); - assert.sameValue(ownKeys.length, 2, "iterator result has two own properties"); - assert.sameValue(ownKeys[0], "value", "first property is 'value'"); - assert.sameValue(ownKeys[1], "done", "second property is 'done'"); - - verifyProperty(result, "value", { - value: value, - writable: true, - enumerable: true, - configurable: true, - }); - - verifyProperty(result, "done", { - value: done, - writable: true, - enumerable: true, - configurable: true, - }); -} - -// Assert |array| is a packed array with default property attributes. -function assertIsPackedArray(array) { - assert(Array.isArray(array), "array is an array exotic object"); - - assert.sameValue( - Object.getPrototypeOf(array), - Array.prototype, - "[[Prototype]] of array is Array.prototype" - ); - - assert(Object.isExtensible(array), "array is extensible"); - - // Ensure "length" property has its default property attributes. - verifyProperty(array, "length", { - writable: true, - enumerable: false, - configurable: false, - }); - - // Ensure no holes and all elements have the default property attributes. - for (var i = 0; i < array.length; i++) { - verifyProperty(array, i, { - writable: true, - enumerable: true, - configurable: true, - }); - } -} - -// Yield all prefixes of the string |s|. -function* prefixes(s) { - for (var i = 0; i <= s.length; ++i) { - yield s.slice(0, i); - } -} - -// Empty iterable doesn't yield any values. -var empty = { - *[Symbol.iterator]() { - } -}; - -// Yield a single value. -var single = { - *[Symbol.iterator]() { - yield 1000; - } -}; - -// Yield an infinite amount of numbers. -var numbers = { - *[Symbol.iterator]() { - var i = 0; - while (true) { - yield 100 + i++; - } - } -}; - -// |iterables| is an array whose elements are array(-like) objects. Pass it as -// the "iterables" argument to |Iterator.zip|, using |options| as the "options" -// argument. -// -// Then iterate over the returned |Iterator.zip| iterator and check all -// returned iteration values have the expected values. -function test(iterables, options) { - var mode = (options && options.mode) || "shortest"; - var padding = options && options.padding; - - var lengths = iterables.map(function(array) { - return array.length; - }); - - var min = Math.min.apply(null, lengths); - var max = Math.max.apply(null, lengths); - - // Expected number of iterations. - var count; - switch (mode) { - case "shortest": - count = min; - break; - case "longest": - count = max; - break; - case "strict": - count = max; - break; - } - - // Compute padding array when |mode| is "longest". - if (mode === "longest") { - if (padding) { - padding = Iterator.from(padding).take(iterables.length).toArray(); - } else { - padding = []; - } - - // Fill with undefined until there are exactly |iterables.length| elements. - padding = padding.concat(Array(iterables.length - padding.length).fill(undefined)); - assert.sameValue(padding.length, iterables.length); - } - - // Last returned elements array. - var last = null; - - var it = Iterator.zip(iterables, options); - for (var i = 0; i < count; i++) { - // "strict" mode throws an error if number of elements don't match. - if (mode === "strict" && min < max && i === min) { - assert.throws(TypeError, function() { - it.next(); - }); - break; - } - - var result = it.next(); - var value = result.value; - - // Test IteratorResult structure. - assertIteratorResult(result, value, false); - - // Ensure value is a new array. - assert.notSameValue(value, last, "returns a new array"); - last = value; - - // Ensure all array elements have the expected value. - var expected = iterables.map(function(array, k) { - if (i < array.length) { - return array[i]; - } - assert.sameValue(mode, "longest", "padding is only used for 'longest' mode"); - return padding[k]; - }); - assert.compareArray(value, expected); - - // Ensure value is a packed array with default data properties. - // - // This operation is destructive, so it has to happen last. - assertIsPackedArray(value); - } - - // Iterator is closed. - assertIteratorResult(it.next(), undefined, true); -} - -var validOptions = [ - undefined, - {}, - {mode: "shortest"}, - {mode: "longest"}, - {mode: "longest", padding: empty}, - {mode: "longest", padding: single}, - {mode: "longest", padding: numbers}, - {mode: "strict"}, -]; - -for (var options of validOptions) { - // Zip an empty iterable. - var it = Iterator.zip([], options); - assertIteratorResult(it.next(), undefined, true); - - // Zip a single iterator. - for (var prefix of prefixes("abcd")) { - // Split prefix into an array. - test([prefix.split("")], options); - - // Use String wrapper as the iterable. - test([new String(prefix)], options); - } - - // Zip two iterators. - for (var prefix1 of prefixes("abcd")) { - for (var prefix2 of prefixes("efgh")) { - // Split prefixes into arrays. - test([prefix1.split(""), prefix2.split("")], options); - - // Use String wrappers as the iterables. - test([new String(prefix1), new String(prefix2)], options); - } - } - - // Zip three iterators. - for (var prefix1 of prefixes("abcd")) { - for (var prefix2 of prefixes("efgh")) { - for (var prefix3 of prefixes("ijkl")) { - // Split prefixes into arrays. - test([prefix1.split(""), prefix2.split(""), prefix3.split("")], options); - - // Use String wrappers as the iterables. - test([new String(prefix1), new String(prefix2), new String(prefix3)], options); - } - } - } -} diff --git a/test/built-ins/Iterator/zip/iterables-containing-string-objects.js b/test/built-ins/Iterator/zip/iterables-containing-string-objects.js new file mode 100644 index 0000000000..12a402e508 --- /dev/null +++ b/test/built-ins/Iterator/zip/iterables-containing-string-objects.js @@ -0,0 +1,17 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zip +description: > + Accepts String objects as inputs. +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var result = Array.from(Iterator.zip([Object("abc"), Object("123")])); + +assert.sameValue(result.length, 3); +assert.compareArray(result[0], ["a", "1"]); +assert.compareArray(result[1], ["b", "2"]); +assert.compareArray(result[2], ["c", "3"]); diff --git a/test/built-ins/Iterator/zip/iterator-non-iterable.js b/test/built-ins/Iterator/zip/iterator-non-iterable.js new file mode 100644 index 0000000000..575a78f88d --- /dev/null +++ b/test/built-ins/Iterator/zip/iterator-non-iterable.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.zip +description: > + Throws a TypeError when the "iterables" argument is not iterable. +features: [joint-iteration] +---*/ + +var invalidIterables = [ + {}, + { next() {}, return() {} }, + function() {} +]; + +// Throws a TypeError for invalid iterables values. +for (var iterables of invalidIterables) { + assert.throws(TypeError, function() { + Iterator.zip(iterables); + }); +} diff --git a/test/built-ins/Iterator/zipKeyed/basic-longest.js b/test/built-ins/Iterator/zipKeyed/basic-longest.js new file mode 100644 index 0000000000..2bf04ab5da --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/basic-longest.js @@ -0,0 +1,105 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Basic Iterator.zipKeyed test with "longest" mode. +includes: [compareArray.js, propertyHelper.js, iteratorZipUtils.js] +features: [joint-iteration] +---*/ + +function testSequence(inputs, inputsLabel, minLength, maxLength) { + function test(options, optionsLabel, getPaddingForInput) { + var label = optionsLabel + ", " + inputsLabel; + var it = Iterator.zipKeyed(inputs, options); + assertZippedKeyed(it, inputs, minLength, label); + + var expectedKeys = Object.keys(inputs); + + for (var i = minLength; i < maxLength; i++) { + var itemLabel = label + ", step " + i; + + var result = it.next(); + var value = result.value; + + // Test IteratorResult structure. + assertIteratorResult(result, value, false, itemLabel); + + // Ensure resulting object has the expected keys and values. + assert.compareArray(Object.keys(value), expectedKeys, itemLabel + ": result object keys"); + + var expectedValues = Object.values(inputs).map(function (input, j) { + return i < input.length ? input[i] : getPaddingForInput(j); + }); + assert.compareArray(Object.values(value), expectedValues, itemLabel + ": result object values"); + } + assertIteratorResult(it.next(), undefined, true, label + ": after completion"); + } + + test( + { mode: "longest" }, + "options = { mode: 'longest' }", + function () { + return undefined; + }, + ); + + test( + { mode: "longest", padding: {} }, + "options = { mode: 'longest', padding: {} }", + function () { + return undefined; + }, + ); + + test( + { mode: "longest", padding: { prop_0: "pad" } }, + "options = { mode: 'longest', padding: { prop_0: 'pad' } }", + function (idx) { + return idx === 0 ? "pad" : undefined; + }, + ); + + test( + { mode: "longest", padding: { prop_1: "pad" } }, + "options = { mode: 'longest', padding: { prop_1: 'pad' } }", + function (idx) { + return idx === 1 ? "pad" : undefined; + }, + ); + + var padding = {}; + for (var key in inputs) { + padding[key] = "pad"; + } + test( + { mode: "longest", padding: padding }, + "options = { mode: 'longest', padding: { prop_0: 'pad', ..., prop_N: 'pad' } }", + function (idx) { + return "pad"; + }, + ); + + // Object with many properties. + padding = new Proxy({}, { + has(target, key) { + return key.indexOf('_') !== -1; + }, + get(target, key, receiver) { + var split = key.split('_'); + if (split.length !== 2) return undefined; + return 'pad_' + split[1]; + } + }); + test( + { mode: "longest", padding: padding }, + "options = { mode: 'longest', padding: { prop_0: 'pad_1', ..., prop_N: 'pad_N' } }", + function (idx) { + return 'pad_' + idx; + }, + ); + +} + +forEachSequenceCombinationKeyed(testSequence); diff --git a/test/built-ins/Iterator/zipKeyed/basic-shortest.js b/test/built-ins/Iterator/zipKeyed/basic-shortest.js new file mode 100644 index 0000000000..b217e09c9e --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/basic-shortest.js @@ -0,0 +1,29 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Basic Iterator.zipkeyed test with "shortest" mode. +includes: [compareArray.js, propertyHelper.js, iteratorZipUtils.js] +features: [joint-iteration] +---*/ + +function testSequence(inputs, inputsLabel, minLength, maxLength) { + function test(options, optionsLabel) { + var label = optionsLabel + ", " + inputsLabel; + var it = Iterator.zipKeyed(inputs, options); + assertZippedKeyed(it, inputs, minLength, label); + + // Iterator is closed after `minLength` items. + assertIteratorResult(it.next(), undefined, true, label + ": after completion"); + } + + test(undefined, "options = undefined"); + + test({}, "options = {}"); + + test({ mode: "shortest" }, "options = { mode: 'shortest' }"); +} + +forEachSequenceCombinationKeyed(testSequence); diff --git a/test/built-ins/Iterator/zipKeyed/basic-strict.js b/test/built-ins/Iterator/zipKeyed/basic-strict.js new file mode 100644 index 0000000000..52dc509835 --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/basic-strict.js @@ -0,0 +1,30 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Basic Iterator.zipkeyed test with "strict" mode. +includes: [compareArray.js, propertyHelper.js, iteratorZipUtils.js] +features: [joint-iteration] +---*/ + +function testSequence(inputs, inputsLabel, minLength, maxLength) { + function test(options, optionsLabel) { + var label = optionsLabel + ", " + inputsLabel; + var it = Iterator.zipKeyed(inputs, options); + assertZippedKeyed(it, inputs, minLength, label); + + if (minLength === maxLength) { + assertIteratorResult(it.next(), undefined, true, label + ": after completion"); + } else { + assert.throws(TypeError, function() { + it.next(); + }, label + " should throw after " + minLength + " items."); + } + } + + test({ mode: "strict" }, "options = { mode: 'strict' }"); +} + +forEachSequenceCombinationKeyed(testSequence); diff --git a/test/built-ins/Iterator/zipKeyed/basic.js b/test/built-ins/Iterator/zipKeyed/basic.js deleted file mode 100644 index 768f844d52..0000000000 --- a/test/built-ins/Iterator/zipKeyed/basic.js +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (C) 2025 André Bargull. All rights reserved. -// This code is governed by the BSD license found in the LICENSE file. - -/*--- -esid: sec-iterator.zipkeyed -description: > - Basic Iterator.zipKeyed test using all three possible modes. -includes: [compareArray.js, propertyHelper.js] -features: [joint-iteration] ----*/ - -// Assert |result| is an object created by CreateIteratorResultObject. -function assertIteratorResult(result, value, done) { - assert.sameValue( - Object.getPrototypeOf(result), - Object.prototype, - "[[Prototype]] of iterator result is Object.prototype" - ); - - assert(Object.isExtensible(result), "iterator result is extensible"); - - var ownKeys = Reflect.ownKeys(result); - assert.sameValue(ownKeys.length, 2, "iterator result has two own properties"); - assert.sameValue(ownKeys[0], "value", "first property is 'value'"); - assert.sameValue(ownKeys[1], "done", "second property is 'done'"); - - verifyProperty(result, "value", { - value: value, - writable: true, - enumerable: true, - configurable: true, - }); - - verifyProperty(result, "done", { - value: done, - writable: true, - enumerable: true, - configurable: true, - }); -} - -// Assert |actual| is a plain object equal to |expected| with default property attributes. -function assertPlainObject(actual, expected) { - assert.sameValue( - Object.getPrototypeOf(actual), - null, - "[[Prototype]] of actual is null" - ); - - assert(Object.isExtensible(actual), "actual is extensible"); - - var actualKeys = Reflect.ownKeys(actual); - var expectedKeys = Reflect.ownKeys(expected); - - // All expected property keys are present. - assert.compareArray(actualKeys, expectedKeys); - - // All expected property values are equal. - for (var key of expectedKeys) { - assert.sameValue(actual[key], expected[key], "with key: " + String(key)); - } - - // Ensure all properties have the default property attributes. - for (var key of expectedKeys) { - verifyProperty(actual, key, { - writable: true, - enumerable: true, - configurable: true, - }); - } -} - -// Yield all prefixes of the string |s|. -function* prefixes(s) { - for (var i = 0; i <= s.length; ++i) { - yield s.slice(0, i); - } -} - -// Empty object. -var empty = {}; - -// Object with a single property. -var single = { - a: 1000, -}; - -// Object with many properties. -var numbers = new Proxy({}, { - has(target, key) { - if (typeof key === "symbol") { - key = key.description; - } - assert.sameValue(key.length, 1, "unsupported key length"); - return true; - }, - get(target, key, receiver) { - if (typeof key === "symbol") { - key = key.description; - } - assert.sameValue(key.length, 1, "unsupported key length"); - return key.charCodeAt(0); - } -}); - -// |iterables| is an object whose properties are array(-like) objects. Pass it -// as the "iterables" argument to |Iterator.zipKeyed|, using |options| as the -// "options" argument. -// -// Then iterate over the returned |Iterator.zipKeyed| iterator and check all -// returned iteration values have the expected values. -function test(iterables, options) { - var mode = (options && options.mode) || "shortest"; - var padding = options && options.padding; - - // Not Object.entries to allow symbol property keys. - var entries = Reflect.ownKeys(iterables).map(function(key) { - return [key, iterables[key]]; - }); - - var lengths = entries.map(function([key, array]) { - return array.length; - }); - - var min = Math.min.apply(null, lengths); - var max = Math.max.apply(null, lengths); - - // Expected number of iterations. - var count; - switch (mode) { - case "shortest": - count = min; - break; - case "longest": - count = max; - break; - case "strict": - count = max; - break; - } - - // Compute padding array when |mode| is "longest". - if (mode === "longest") { - padding = entries.map(function([key, array]) { - if (padding !== undefined && key in padding) { - return padding[key]; - } - return undefined; - }); - } - - // Last returned results object. - var last = null; - - var it = Iterator.zipKeyed(iterables, options); - for (var i = 0; i < count; i++) { - // "strict" mode throws an error if number of elements don't match. - if (mode === "strict" && min < max && i === min) { - assert.throws(TypeError, function() { - it.next(); - }); - break; - } - - var result = it.next(); - var value = result.value; - - // Test IteratorResult structure. - assertIteratorResult(result, value, false); - - // Ensure value is a new object. - assert.notSameValue(value, last, "returns a new object"); - last = value; - - var expected = Object.fromEntries(entries.map(function([key, array], k) { - if (i < array.length) { - return [key, array[i]]; - } - assert.sameValue(mode, "longest", "padding is only used for 'longest' mode"); - return [key, padding[k]]; - })); - - // Ensure all properties have the expected value. - // Ensure value is a plain with default data properties. - assertPlainObject(value, expected); - } - - // Iterator is closed. - assertIteratorResult(it.next(), undefined, true); -} - -var validOptions = [ - undefined, - {}, - {mode: "shortest"}, - {mode: "longest"}, - {mode: "longest", padding: empty}, - {mode: "longest", padding: single}, - {mode: "longest", padding: numbers}, - {mode: "strict"}, -]; - -for (var options of validOptions) { - // Zip an empty object. - var it = Iterator.zipKeyed({}, options); - assertIteratorResult(it.next(), undefined, true); - - // Zip a single key. - for (var prefix of prefixes("abcd")) { - // Split prefix into an array. - test({ - a: prefix.split(""), - }, options); - test({ - [Symbol("a")]: prefix.split(""), - }, options); - - // Use String wrapper as the iterable. - test({ - a: new String(prefix), - }, options); - } - - // Zip two keys. - for (var prefix1 of prefixes("abcd")) { - for (var prefix2 of prefixes("efgh")) { - // Split prefixes into arrays. - test({ - a: prefix1.split(""), - b: prefix2.split(""), - }, options); - test({ - [Symbol("a")]: prefix1.split(""), - [Symbol("b")]: prefix2.split(""), - }, options); - - // Use String wrappers as the iterables. - test({ - a: new String(prefix1), - b: new String(prefix2), - }, options); - } - } - - // Zip three keys. - for (var prefix1 of prefixes("abcd")) { - for (var prefix2 of prefixes("efgh")) { - for (var prefix3 of prefixes("ijkl")) { - // Split prefixes into arrays. - test({ - a: prefix1.split(""), - b: prefix2.split(""), - c: prefix3.split(""), - }, options); - test({ - [Symbol("a")]: prefix1.split(""), - [Symbol("b")]: prefix2.split(""), - [Symbol("c")]: prefix3.split(""), - }, options); - - // Use String wrappers as the iterables. - test({ - a: new String(prefix1), - b: new String(prefix2), - c: new String(prefix3), - }, options); - } - } - } -} diff --git a/test/built-ins/Iterator/zipKeyed/iterables-containing-string-objects.js b/test/built-ins/Iterator/zipKeyed/iterables-containing-string-objects.js new file mode 100644 index 0000000000..3ca694ca6c --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-containing-string-objects.js @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipKeyed +description: > + Accepts String objects as inputs. +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var result = Array.from(Iterator.zipKeyed({ + a: Object("abc"), + b: Object("123"), +})); + +assert.sameValue(result.length, 3); +result.forEach(function (object) { + assert.compareArray(Object.keys(object), ["a", "b"]); +}); +assert.compareArray(Object.values(result[0]), ["a", "1"]); +assert.compareArray(Object.values(result[1]), ["b", "2"]); +assert.compareArray(Object.values(result[2]), ["c", "3"]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-enumerable.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-enumerable.js index 86d2dc2cf4..cddbadab7b 100644 --- a/test/built-ins/Iterator/zipKeyed/iterables-iteration-enumerable.js +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-enumerable.js @@ -38,7 +38,7 @@ var iterables = Object.create(null, { enumerable: false, }); - return []; + return ['value for b']; } }, c: { @@ -58,7 +58,7 @@ var iterables = Object.create(null, { enumerable: true, }); - return []; + return ['value for d']; } }, e: { @@ -66,15 +66,19 @@ var iterables = Object.create(null, { configurable: true, get() { log.push("get e"); - return []; + return ['value for e']; } }, }); -Iterator.zipKeyed(iterables); +var result = Array.from(Iterator.zipKeyed(iterables)); assert.compareArray(log, [ "get b", "get d", "get e", ]); + +assert.sameValue(result.length, 1); +assert.compareArray(Object.keys(result[0]), ["b", "d", "e"]); +assert.compareArray(Object.values(result[0]), ["value for b", "value for d", "value for e"]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-inherited.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-inherited.js new file mode 100644 index 0000000000..15e49691cb --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-inherited.js @@ -0,0 +1,27 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.zipkeyed +description: > + Inherited properties are skipped in "iterables" iteration. +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var parent = { + get a() { + throw new Test262Error("inherited properties should not be examined"); + }, +} + +var iterables = { + __proto__: parent, + b: ['value for b'], +}; + +var result = Array.from(Iterator.zipKeyed(iterables)); + +assert.sameValue(result.length, 1); +assert.compareArray(Object.keys(result[0]), ["b"]); +assert.compareArray(Object.values(result[0]), ["value for b"]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-symbol-key.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-symbol-key.js new file mode 100644 index 0000000000..e6b5ce911a --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-symbol-key.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.zipkeyed +description: > + Symbol properties are used during "iterables" iteration. +includes: [compareArray.js] +features: [joint-iteration] +---*/ + +var symbolA = Symbol('a'); + +var iterables = { + [symbolA]: ['value for a'], + b: ['value for b'], +}; + +var result = Array.from(Iterator.zipKeyed(iterables)); + +assert.sameValue(result.length, 1); +assert.compareArray(Reflect.ownKeys(result[0]), ["b", symbolA]); +assert.sameValue(result[0].b, "value for b"); +assert.sameValue(result[0][symbolA], "value for a"); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration-undefined.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration-undefined.js new file mode 100644 index 0000000000..6a7bec78ef --- /dev/null +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration-undefined.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.zipkeyed +description: > + undefined-valued properties are skipped in "iterables" iteration. +includes: [compareArray.js] +features: [joint-iteration] +---*/ + + +var iterables = { + a: undefined, + b: ['value for b'], +}; + +var result = Array.from(Iterator.zipKeyed(iterables)); + +assert.sameValue(result.length, 1); +assert.compareArray(Reflect.ownKeys(result[0]), ["b"]); +assert.compareArray(Object.values(result[0]), ["value for b"]); diff --git a/test/built-ins/Iterator/zipKeyed/iterables-iteration.js b/test/built-ins/Iterator/zipKeyed/iterables-iteration.js index 7b960622b6..7890602742 100644 --- a/test/built-ins/Iterator/zipKeyed/iterables-iteration.js +++ b/test/built-ins/Iterator/zipKeyed/iterables-iteration.js @@ -42,11 +42,6 @@ var iterableReturningThrowingIterator = { } }; -// |undefined| values are ignored. -Iterator.zipKeyed({ - a: undefined, -}); - // GetIteratorFlattenable accepts both iterables and iterators. Iterator.zipKeyed({ a: throwingIterator, @@ -61,6 +56,7 @@ var badIterators = [ Symbol(), 0, 0n, + // undefined is handled separately ]; for (var iterator of badIterators) {