Skip to content
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer we don't compute a large list of expected results inline in a test using what is essentially a mini-polyfill. When the test fails, it's unclear whether the implementation or the mini-polyfill is wrong.

There was some more discussion on this re. locale-sensitive testing in the talk I presented in the April TC39 meeting. slides, discussion

Same for zipKeyed/basic.js.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is really a mini-polyfill, just verifying several properties at once. I've split it into two tests + a new harness file, if you think that's an improvement: anba#1

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