Skip to content
Merged
129 changes: 129 additions & 0 deletions test/built-ins/Iterator/concat/completed-after-abrupt-completion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (C) 2026 Saúl Ibarra Corretgé. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-iterator.concat
description: >
The iterator helper is completed after an abrupt completion
features: [iterator-sequencing]
---*/

// The closure of Iterator.concat is the body of a generator, so an abrupt
// completion from it completes the generator: a later next() returns an
// undefined, done result without stepping the iterator again, the iterables that
// follow are never opened, and a later return() is not forwarded either.
function concatWithStep(next) {
const calls = {
nextCalls: 0,
returnCalls: 0,
openCalls: 0,
};

const iterable = {
[Symbol.iterator]() {
return {
next() {
++calls.nextCalls;
return next();
},
return() {
++calls.returnCalls;
return {};
},
};
}
};

const laterIterable = {
[Symbol.iterator]() {
++calls.openCalls;
return {
next() {
return {
done: false,
value: 1,
};
},
};
}
};

return {
calls,
iterator: Iterator.concat(iterable, laterIterable),
};
}

function assertCompleted(concat) {
const resultAfterThrow = concat.iterator.next();

assert.sameValue(resultAfterThrow.done, true, 'the iterator helper is completed');
assert.sameValue(resultAfterThrow.value, undefined, 'the iterator helper is completed');
assert.sameValue(concat.calls.nextCalls, 1, 'the underlying iterator is not stepped again');
assert.sameValue(concat.calls.openCalls, 0, 'the remaining iterables are never opened');

// Only the calls made from here on are counted, so that this assertion does
// not overlap with the one made in
// underlying-iterator-not-closed-when-step-throws.js.
const returnCallsBefore = concat.calls.returnCalls;
const resultAfterReturn = concat.iterator.return();

assert.sameValue(resultAfterReturn.done, true, 'the iterator helper is completed');
assert.sameValue(resultAfterReturn.value, undefined, 'the iterator helper is completed');
assert.sameValue(concat.calls.returnCalls, returnCallsBefore,
'return is not forwarded to the underlying iterator');
}

// Underlying iterator has a throwing next method.
const concatWithThrowingNext = concatWithStep(function() {
throw new Test262Error();
});

assert.throws(Test262Error, function() {
concatWithThrowingNext.iterator.next();
});

assertCompleted(concatWithThrowingNext);

// Underlying iterator next returns an object with a throwing done getter.
const concatWithThrowingDoneGetter = concatWithStep(function() {
return {
get done() {
throw new Test262Error();
},
value: 1,
};
});

assert.throws(Test262Error, function() {
concatWithThrowingDoneGetter.iterator.next();
});

assertCompleted(concatWithThrowingDoneGetter);

// Underlying iterator next returns an object with a throwing value getter.
const concatWithThrowingValueGetter = concatWithStep(function() {
return {
done: false,
get value() {
throw new Test262Error();
},
};
});

assert.throws(Test262Error, function() {
concatWithThrowingValueGetter.iterator.next();
});

assertCompleted(concatWithThrowingValueGetter);

// Underlying iterator next returns a non-object.
const concatWithNonObjectResult = concatWithStep(function() {
return null;
});

assert.throws(TypeError, function() {
concatWithNonObjectResult.iterator.next();
});

assertCompleted(concatWithNonObjectResult);
54 changes: 54 additions & 0 deletions test/built-ins/Iterator/concat/iterator-has-no-return.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (C) 2026 Saúl Ibarra Corretgé. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-iterator.concat
description: >
The underlying iterator is sometimes unable to be closed (has no return method)
features: [iterator-sequencing]
---*/

// GetMethod returns undefined for a missing return property and for a null one,
// and IteratorClose then leaves the iterator alone and evaluates to the return
// completion it was given.
function concatWithIterator(testIterator) {
return Iterator.concat({
[Symbol.iterator]() {
return testIterator;
}
});
}

function next() {
return {
done: false,
value: 1,
};
}

// No return property at all.
const iteratorWithoutReturn = concatWithIterator({ next });
iteratorWithoutReturn.next();

const resultWithoutReturn = iteratorWithoutReturn.return();

assert.sameValue(resultWithoutReturn.done, true);
assert.sameValue(resultWithoutReturn.value, undefined);

// An undefined return property.
const iteratorWithUndefinedReturn = concatWithIterator({ next, return: undefined });
iteratorWithUndefinedReturn.next();

const resultWithUndefinedReturn = iteratorWithUndefinedReturn.return();

assert.sameValue(resultWithUndefinedReturn.done, true);
assert.sameValue(resultWithUndefinedReturn.value, undefined);

// A null return property.
const iteratorWithNullReturn = concatWithIterator({ next, return: null });
iteratorWithNullReturn.next();

const resultWithNullReturn = iteratorWithNullReturn.return();

assert.sameValue(resultWithNullReturn.done, true);
assert.sameValue(resultWithNullReturn.value, undefined);
49 changes: 49 additions & 0 deletions test/built-ins/Iterator/concat/result-is-iterator-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (C) 2026 Saúl Ibarra Corretgé. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-iterator.concat
description: >
The return value of Iterator.concat is an Iterator Helper
features: [iterator-sequencing, iterator-helpers]
---*/

// Iterator.concat builds its result with %IteratorHelperPrototype%, so it shares
// its prototype, and with it its next and return methods and its @@toStringTag,
// with the iterator helpers returned by %Iterator.prototype%.map and friends.
const iteratorHelperPrototype = Object.getPrototypeOf([].values().map(v => v));

const iteratorWithoutArguments = Iterator.concat();

assert.sameValue(
Object.getPrototypeOf(iteratorWithoutArguments),
iteratorHelperPrototype,
'Object.getPrototypeOf(Iterator.concat()) must return %IteratorHelperPrototype%'
);
assert.sameValue(iteratorWithoutArguments.next, iteratorHelperPrototype.next);
assert.sameValue(iteratorWithoutArguments.return, iteratorHelperPrototype.return);
assert.sameValue(Object.prototype.toString.call(iteratorWithoutArguments), '[object Iterator Helper]');

const iterable = {
[Symbol.iterator]() {
return {
next() {
return {
done: true,
value: undefined,
};
},
};
}
};

const iteratorWithIterable = Iterator.concat(iterable);

assert.sameValue(
Object.getPrototypeOf(iteratorWithIterable),
iteratorHelperPrototype,
'Object.getPrototypeOf(Iterator.concat(iterable)) must return %IteratorHelperPrototype%'
);
assert.sameValue(iteratorWithIterable.next, iteratorHelperPrototype.next);
assert.sameValue(iteratorWithIterable.return, iteratorHelperPrototype.return);
assert.sameValue(Object.prototype.toString.call(iteratorWithIterable), '[object Iterator Helper]');
43 changes: 43 additions & 0 deletions test/built-ins/Iterator/concat/return-method-not-callable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (C) 2026 Saúl Ibarra Corretgé. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-iterator.concat
description: >
Throws a TypeError when the underlying iterator's return property is not callable
features: [iterator-sequencing]
---*/

const testIterator = {
next() {
return {
done: false,
value: 1,
};
},
return: 1,
};

const iterator = Iterator.concat({
[Symbol.iterator]() {
return testIterator;
}
});

iterator.next();

assert.throws(TypeError, function() {
iterator.return();
});

// The iterator helper is completed even though closing the underlying iterator
// failed.
const resultAfterThrow = iterator.next();

assert.sameValue(resultAfterThrow.done, true);
assert.sameValue(resultAfterThrow.value, undefined);

const resultAfterReturn = iterator.return();

assert.sameValue(resultAfterReturn.done, true);
assert.sameValue(resultAfterReturn.value, undefined);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2026 Saúl Ibarra Corretgé. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-iterator.concat
description: >
Throws a TypeError when the underlying iterator's return method returns a non-object
features: [iterator-sequencing]
---*/

let returnCalls = 0;

const testIterator = {
next() {
return {
done: false,
value: 1,
};
},
return() {
++returnCalls;
return 1;
},
};

const iterator = Iterator.concat({
[Symbol.iterator]() {
return testIterator;
}
});

iterator.next();

assert.throws(TypeError, function() {
iterator.return();
});

assert.sameValue(returnCalls, 1);

// The iterator helper is completed even though closing the underlying iterator
// failed.
const resultAfterThrow = iterator.next();

assert.sameValue(resultAfterThrow.done, true);
assert.sameValue(resultAfterThrow.value, undefined);

const resultAfterReturn = iterator.return();

assert.sameValue(resultAfterReturn.done, true);
assert.sameValue(resultAfterReturn.value, undefined);
assert.sameValue(returnCalls, 1, 'return is not called again on the underlying iterator');
51 changes: 51 additions & 0 deletions test/built-ins/Iterator/concat/return-method-throws.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2026 Saúl Ibarra Corretgé. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-iterator.concat
description: >
Underlying iterator has a throwing return method
features: [iterator-sequencing]
---*/

let returnCalls = 0;

const testIterator = {
next() {
return {
done: false,
value: 1,
};
},
return() {
++returnCalls;
throw new Test262Error();
},
};

const iterator = Iterator.concat({
[Symbol.iterator]() {
return testIterator;
}
});

iterator.next();

assert.throws(Test262Error, function() {
iterator.return();
});

assert.sameValue(returnCalls, 1);

// The iterator helper is completed even though closing the underlying iterator
// failed.
const resultAfterThrow = iterator.next();

assert.sameValue(resultAfterThrow.done, true);
assert.sameValue(resultAfterThrow.value, undefined);

const resultAfterReturn = iterator.return();

assert.sameValue(resultAfterReturn.done, true);
assert.sameValue(resultAfterReturn.value, undefined);
assert.sameValue(returnCalls, 1, 'return is not called again on the underlying iterator');
Loading
Loading