Skip to content

Commit

Permalink
[Fix] IteratorZip AO: allow an empty list of iterators
Browse files Browse the repository at this point in the history
 - also, add tests for `zipKeyed`
  • Loading branch information
ljharb committed Dec 3, 2024
1 parent 7f8a1c0 commit 265e566
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 8 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"ToBoolean",
"ToIntegerOrInfinity",
"ToNumber",
"ToPropertyDescriptor",
"Type",
],
}],
Expand Down
12 changes: 6 additions & 6 deletions Iterator.zipKeyed/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var IsDataDescriptor = require('es-abstract/2024/IsDataDescriptor');
var IteratorZip = require('../aos/IteratorZip');
var OrdinaryObjectCreate = require('es-abstract/2024/OrdinaryObjectCreate');
var ThrowCompletion = require('es-abstract/2024/ThrowCompletion');
var ToPropertyDescriptor = require('es-abstract/2024/ToPropertyDescriptor');
var Type = require('es-abstract/2024/Type');

var forEach = require('es-abstract/helpers/forEach');
Expand Down Expand Up @@ -43,10 +44,10 @@ module.exports = function zipKeyed(iterables) {

var paddingOption; // step 6

if (mode === 'longest') {
paddingOption = Get(options, 'padding'); // step 7
if (mode === 'longest') { // step 7
paddingOption = Get(options, 'padding'); // step 7.a
if (typeof paddingOption !== 'undefined' && Type(paddingOption) !== 'Object') {
throw new $TypeError('`padding` option must be an Object'); // step 7.1
throw new $TypeError('`padding` option must be an Object'); // step 7.b
}
}

Expand All @@ -61,7 +62,7 @@ module.exports = function zipKeyed(iterables) {
forEach(allKeys, function (key) { // step 12
var desc;
try {
desc = gOPD(iterables, key); // step 12.a
desc = ToPropertyDescriptor(gOPD(iterables, key)); // step 12.a
} catch (e) {
IfAbruptCloseIterators(ThrowCompletion(e), iters); // step 12.b
}
Expand Down Expand Up @@ -92,8 +93,7 @@ module.exports = function zipKeyed(iterables) {
try {
iter = GetIteratorFlattenable(value, 'REJECT-STRINGS'); // step 12.c.iv.2
} catch (e) {
// step 12.c.iv.3
// 3. IfAbruptCloseIterators(e, iters).
IfAbruptCloseIterators(ThrowCompletion(e), iters); // step 12.c.iv.3
}
iters[iters.length] = iter; // step 12.c.iv.4
}
Expand Down
4 changes: 2 additions & 2 deletions aos/IteratorZip.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var ThrowCompletion = require('es-abstract/2024/ThrowCompletion');
var isAbstractClosure = require('es-abstract/helpers/isAbstractClosure');
var IsArray = require('es-abstract/helpers/IsArray');
var isIteratorRecord = require('es-abstract/helpers/records/iterator-record');
var some = require('es-abstract/helpers/some');
var every = require('es-abstract/helpers/every');

var callBound = require('call-bind/callBound');

Expand All @@ -27,7 +27,7 @@ var SLOT = require('internal-slot');
// https://tc39.es/proposal-joint-iteration/#sec-IteratorZip

module.exports = function IteratorZip(iters, mode, padding, finishResults) {
if (!IsArray(iters) || !some(iters, isIteratorRecord)) {
if (!IsArray(iters) || !every(iters, isIteratorRecord)) {
throw new $TypeError('`iters` must be a List of IteratorRecords');
}

Expand Down
151 changes: 151 additions & 0 deletions test/Iterator.zipKeyed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
'use strict';

var defineProperties = require('define-properties');
var test = require('tape');
var callBind = require('call-bind');
var functionsHaveNames = require('functions-have-names')();
var forEach = require('for-each');
var debug = require('object-inspect');
var v = require('es-value-fixtures');
var hasSymbols = require('has-symbols/shams')();
var mockProperty = require('mock-property');

var index = require('../Iterator.zipKeyed');
var impl = require('../Iterator.zipKeyed/implementation');
var from = require('../Iterator.from/polyfill')();

var testIterator = require('./helpers/testIterator');

var isEnumerable = Object.prototype.propertyIsEnumerable;

module.exports = {
tests: function (zipKeyed, name, t) {
t['throws'](
function () { return new zipKeyed(); }, // eslint-disable-line new-cap
TypeError,
'`' + name + '` itself is not a constructor'
);
t['throws'](
function () { return new zipKeyed({}); }, // eslint-disable-line new-cap
TypeError,
'`' + name + '` itself is not a constructor, with an argument'
);

forEach(v.primitives, function (primitive) {
t['throws'](
function () { zipKeyed(primitive); },
TypeError,
debug(primitive) + ' is not an Object'
);
if (primitive != null) {
t['throws'](
function () { zipKeyed({ a: primitive }); },
TypeError,
'key "a" on iterables object is ' + debug(primitive) + ' which is not an iterable Object'
);
}
});

forEach(v.objects, function (nonIterator) {
t.doesNotThrow(function () { zipKeyed({ a: nonIterator }); }, 'does not throw until `.next()`');

t['throws'](
function () { zipKeyed({ a: nonIterator }).next(); },
TypeError,
'key "a" on iterables object is ' + debug(nonIterator) + ' which is not an iterable Object'
);
});

t.test('actual iteration', { skip: !hasSymbols }, function (st) {
forEach(v.nonFunctions, function (nonFunction) {
if (nonFunction != null) {
var badIterable = {};
badIterable[Symbol.iterator] = nonFunction;
st['throws'](
function () { zipKeyed({ a: [], b: badIterable, c: [] }).next(); },
TypeError,
'key "b" on iterables object is ' + debug(badIterable) + ' is not a function'
);
}
});

forEach(v.strings, function (string) {
st['throws'](
function () { zipKeyed({ a: string }); },
TypeError,
'key "a" on iterables object is an iterable primitive, but non-objects are not considered iterable'
);
});

st.test('real iterators', { skip: !hasSymbols }, function (s2t) {
var iter = [['a', 1], ['b', 2]][Symbol.iterator]();
var iterator = zipKeyed({ a: iter, b: ['a', 3], c: ['b', 4] });

testIterator(
iterator,
[
{ __proto__: null, a: ['a', 1], b: 'a', c: 'b' },
{ __proto__: null, a: ['b', 2], b: 3, c: 4 }
],
s2t,
'array iterator + array yields combined results'
);

s2t.end();
});

st.test('observability in a replaced String iterator', function (s2t) {
var originalStringIterator = String.prototype[Symbol.iterator];
var observedType;
s2t.teardown(mockProperty(String.prototype, Symbol.iterator, {
get: function () {
'use strict'; // eslint-disable-line strict, lines-around-directive

observedType = typeof this;
return originalStringIterator;
}
}));

zipKeyed([from('')]);
s2t.equal(observedType, 'string', 'string primitive -> primitive receiver in Symbol.iterator getter');
zipKeyed([from(Object(''))]);
s2t.equal(observedType, 'object', 'boxed string -> boxed string in Symbol.iterator getter');

s2t.end();
});

st.end();
});
},
index: function () {
test('Iterator.zipKeyed: index', function (t) {
module.exports.tests(index, 'Iterator.zipKeyed', t);

t.end();
});
},
implementation: function () {
test('Iterator.zipKeyed: implementation', function (t) {
module.exports.tests(impl, 'Iterator.zipKeyed', t);

t.end();
});
},
shimmed: function () {
test('Iterator.zipKeyed: shimmed', function (t) {
t.test('Function name', { skip: !functionsHaveNames }, function (st) {
st.equal(Iterator.zipKeyed.name, 'zipKeyed', 'Iterator.zipKeyed has name "zipKeyed"');
st.end();
});

t.test('enumerability', { skip: !defineProperties.supportsDescriptors }, function (et) {
et.equal(false, isEnumerable.call(Iterator, 'zipKeyed'), 'Iterator.zipKeyed is not enumerable');
et.end();
});

module.exports.tests(callBind(Iterator.zipKeyed, Iterator), 'Iterator.zipKeyed', t);

t.end();
});
}
};

0 comments on commit 265e566

Please sign in to comment.