Skip to content

Add tests for Joint Iteration proposal #4528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ canonical-tz
# https://github.com/tc39/proposal-upsert
upsert

# Joint Iteration
# https://github.com/tc39/proposal-joint-iteration
joint-iteration

## Standard language features
#
# Language features that have been included in a published version of the
Expand Down
236 changes: 236 additions & 0 deletions test/built-ins/Iterator/zip/basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// 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);
}
}
}
}
15 changes: 15 additions & 0 deletions test/built-ins/Iterator/zip/is-function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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: >
Iterator.zip is a built-in function
features: [joint-iteration]
---*/

assert.sameValue(
typeof Iterator.zip,
"function",
"The value of `typeof Iterator.zip` is 'function'"
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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: >
Perform iteration of the "iterables" argument after reading all properties.
info: |
Iterator.zip ( iterables [ , options ] )
...
3. Let mode be ? Get(options, "mode").
...
7. If mode is "longest", then
a. Set paddingOption to ? Get(options, "padding").
...
10. Let inputIter be ? GetIterator(iterables, sync).
...
includes: [compareArray.js]
features: [joint-iteration]
---*/

var log = [];

var iterables = {
[Symbol.iterator]() {
log.push("get iterator");
return this;
},
next() {
return {done: true};
}
};

var options = {
get mode() {
log.push("get mode");
return "longest";
},
get padding() {
log.push("get padding");
return [];
}
};

Iterator.zip(iterables, options);

assert.compareArray(log, [
"get mode",
"get padding",
"get iterator",
]);
Loading