300 lines
13 KiB
HTML
300 lines
13 KiB
HTML
<!-- Any copyright is dedicated to the Public Domain.
|
|
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
|
<!DOCTYPE HTML>
|
|
<html>
|
|
<head>
|
|
<title>Test Async Iterable Interface</title>
|
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
|
</head>
|
|
<body>
|
|
<script class="testbody" type="application/javascript">
|
|
|
|
add_task(async function init() {
|
|
await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
|
|
});
|
|
|
|
const singleValues = Array(10).fill(0).map((_, i) => i * 9 % 7);
|
|
|
|
async function check_single_result_values(values, multiplier = 1) {
|
|
is(values.length, 10, `AsyncIterableSingle: should return 10 elements`);
|
|
for (let i = 0; i < 10; i++) {
|
|
let expected = singleValues[i] * multiplier;
|
|
is(values[i], expected,
|
|
`AsyncIterableSingle: should be ${expected}, get ${values[i]}`);
|
|
}
|
|
}
|
|
|
|
async function check_single_result(itr, multiplier = 1) {
|
|
let values = [];
|
|
for await (let v of itr) {
|
|
values.push(v);
|
|
}
|
|
check_single_result_values(values, multiplier);
|
|
}
|
|
|
|
async function test_data_single() {
|
|
info(`AsyncIterableSingle: Testing simple iterable creation and functionality`);
|
|
|
|
// eslint-disable-next-line no-undef
|
|
let itr = new TestInterfaceAsyncIterableSingle({ failToInit: true });
|
|
let initFailed = false;
|
|
try {
|
|
itr.values();
|
|
} catch (e) {
|
|
initFailed = true;
|
|
}
|
|
ok(initFailed,
|
|
"AsyncIterableSingle: A failure in asynchronous iterator initialization " +
|
|
"steps should propagate to the caller of the asynchronous iterator's " +
|
|
"constructor.");
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingle();
|
|
is(itr.values, itr[Symbol.asyncIterator],
|
|
`AsyncIterableSingle: Should be using @@asyncIterator for 'values'`);
|
|
|
|
await check_single_result(itr);
|
|
await check_single_result(itr.values());
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingleWithArgs();
|
|
is(itr.values, itr[Symbol.asyncIterator],
|
|
`AsyncIterableSingleWithArgs: Should be using @@asyncIterator for 'values'`);
|
|
|
|
await check_single_result(itr, 1);
|
|
await check_single_result(itr.values({ multiplier: 2 }), 2);
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingle();
|
|
let itrValues = itr.values();
|
|
let values = [];
|
|
for (let i = 0; i < 10; ++i) {
|
|
values.push(itrValues.next());
|
|
}
|
|
check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value)));
|
|
|
|
// Test that there is only one ongoing promise at a time.
|
|
// Async iterables return a promise that is then resolved with the iterator
|
|
// value. We create an array of unresolved promises here, one promise for
|
|
// every result that we expect from the iterator. We pass that array of
|
|
// promises to the .value() method of the
|
|
// TestInterfaceAsyncIterableSingleWithArgs, and it will chain the resolving
|
|
// of each resulting iterator value on the corresponding promise from this
|
|
// array. We then resolve the promises in the array one by one in reverse
|
|
// order. This tries to make sure that the iterator always resolves the
|
|
// promises in the order of iteration.
|
|
let unblockers = [];
|
|
let blockingPromises = [];
|
|
for (let i = 0; i < 10; ++i) {
|
|
let unblocker;
|
|
let promise = new Promise((resolve) => {
|
|
unblocker = resolve;
|
|
});
|
|
unblockers.push(unblocker);
|
|
blockingPromises.push(promise);
|
|
}
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingleWithArgs();
|
|
itrValues = itr.values({ blockingPromises });
|
|
values = [];
|
|
for (let i = 0; i < 10; ++i) {
|
|
values.push(itrValues.next());
|
|
}
|
|
unblockers.reverse();
|
|
for (let unblocker of unblockers) {
|
|
unblocker();
|
|
}
|
|
|
|
check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value)));
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingleWithArgs();
|
|
|
|
let callCount = itr.returnCallCount;
|
|
|
|
let i = 0;
|
|
for await (let v of itr) {
|
|
if (++i > 1) {
|
|
break;
|
|
}
|
|
values.push(v);
|
|
}
|
|
|
|
is(itr.returnCallCount, callCount + 1,
|
|
`AsyncIterableSingle: breaking out of for-await-of loop should call "return"`);
|
|
is(itr.returnLastCalledWith, undefined,
|
|
`AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm should be called with the argument that was passed in.`);
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingleWithArgs();
|
|
|
|
async function * yieldFromIterator () {
|
|
yield * itr
|
|
}
|
|
|
|
let yieldingIterator = yieldFromIterator();
|
|
|
|
let result = await yieldingIterator.next();
|
|
is(result.value, singleValues[0],
|
|
`AsyncIterableSingle: should be ${singleValues[0]}, get ${result.value}`);
|
|
result = await yieldingIterator.next();
|
|
is(result.value, singleValues[1],
|
|
`AsyncIterableSingle: should be ${singleValues[1]}, get ${result.value}`);
|
|
|
|
result = await yieldingIterator.return("abcd");
|
|
is(typeof result, "object",
|
|
`AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
|
|
is(result.done, true,
|
|
`AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
|
|
is(result.value, "abcd",
|
|
`AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
|
|
is(itr.returnLastCalledWith, "abcd",
|
|
`AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm should be called with the argument that was passed in.`);
|
|
|
|
result = await yieldingIterator.return("efgh");
|
|
is(typeof result, "object",
|
|
`AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
|
|
is(result.done, true,
|
|
`AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
|
|
is(result.value, "efgh",
|
|
`AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
|
|
is(itr.returnLastCalledWith, "abcd",
|
|
`AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm shouldn't be called if the iterator's 'is finished' flag is true already.`);
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingleWithArgs();
|
|
itrValues = itr.values({ failNextAfter: 1 });
|
|
await itrValues.next().then(({ value, done }) => {
|
|
is(value, singleValues[0], "First value is correct");
|
|
ok(!done, "Expecting more values");
|
|
return itrValues.next();
|
|
}).then(() => {
|
|
ok(false, "Second call to next() should convert failure to a rejected promise.");
|
|
return itrValues.next();
|
|
}).catch(() => {
|
|
ok(true, "Second call to next() should convert failure to a rejected promise.");
|
|
return itrValues.next();
|
|
}).then(({ done }) => {
|
|
ok(done, "An earlier failure in next() should set the async iterator's 'is finished' flag to true.");
|
|
}).catch(() => {
|
|
ok(false, "An earlier failure in next() shouldn't cause subsequent calls to return a rejected promise.");
|
|
});
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingleWithArgs();
|
|
itrValues = itr.values({ throwFromNext: true });
|
|
await itrValues.next().then(() => {
|
|
ok(false, "Should have rejected from the exception");
|
|
}).catch(() => {
|
|
ok(true, "Should have rejected from the exception");
|
|
});
|
|
|
|
// eslint-disable-next-line no-undef
|
|
itr = new TestInterfaceAsyncIterableSingleWithArgs();
|
|
itrValues = itr.values({ throwFromReturn: () => { throw new DOMException("Throw from return", "InvalidStateError"); } });
|
|
await itrValues.return().then(() => {
|
|
ok(false, "Should have rejected from the exception");
|
|
}).catch(() => {
|
|
ok(true, "Should have rejected from the exception");
|
|
});
|
|
}
|
|
|
|
async function test_data_double() {
|
|
info(`AsyncIterableDouble: Testing simple iterable creation and functionality`);
|
|
|
|
// eslint-disable-next-line no-undef
|
|
let itr = new TestInterfaceAsyncIterableDouble();
|
|
is(itr.entries, itr[Symbol.asyncIterator],
|
|
`AsyncIterableDouble: Should be using @@asyncIterator for 'entries'`);
|
|
|
|
let elements = [["a", "b"], ["c", "d"], ["e", "f"]];
|
|
let key_itr = itr.keys();
|
|
let value_itr = itr.values();
|
|
let entries_itr = itr.entries();
|
|
let key = await key_itr.next();
|
|
let value = await value_itr.next();
|
|
let entry = await entries_itr.next();
|
|
for (let i = 0; i < 3; ++i) {
|
|
is(key.value, elements[i][0], `AsyncIterableDouble: Key.value should be ${elements[i][0]}, got ${key.value}`);
|
|
is(key.done, false, `AsyncIterableDouble: Key.done should be false, got ${key.done}`);
|
|
is(value.value, elements[i][1], `AsyncIterableDouble: Value.value should be ${elements[i][1]}, got ${value.value}`);
|
|
is(value.done, false, `AsyncIterableDouble: Value.done should be false, got ${value.done}`);
|
|
is(entry.value[0], elements[i][0], `AsyncIterableDouble: Entry.value[0] should be ${elements[i][0]}, got ${entry.value[0]}`);
|
|
is(entry.value[1], elements[i][1], `AsyncIterableDouble: Entry.value[1] should be ${elements[i][1]}, got ${entry.value[1]}`);
|
|
is(entry.done, false, `AsyncIterableDouble: Entry.done should be false, got ${entry.done}`);
|
|
|
|
key = await key_itr.next();
|
|
value = await value_itr.next();
|
|
entry = await entries_itr.next();
|
|
}
|
|
is(key.value, undefined, `AsyncIterableDouble: Key.value should be ${undefined}, got ${key.value}`);
|
|
is(key.done, true, `AsyncIterableDouble: Key.done should be true, got ${key.done}`);
|
|
is(value.value, undefined, `AsyncIterableDouble: Value.value should be ${undefined}, got ${value.value}`);
|
|
is(value.done, true, `AsyncIterableDouble: Value.done should be true, got ${value.done}`);
|
|
is(entry.value, undefined, `AsyncIterableDouble: Entry.value should be ${undefined}, got ${entry.value}`);
|
|
is(entry.done, true, `AsyncIterableDouble: Entry.done should be true, got ${entry.done}`);
|
|
|
|
let idx = 0;
|
|
for await (let [itrkey, itrvalue] of itr) {
|
|
is(itrkey, elements[idx][0], `AsyncIterableDouble: Looping at ${idx} should have key ${elements[idx][0]}, got ${key}`);
|
|
is(itrvalue, elements[idx][1], `AsyncIterableDouble: Looping at ${idx} should have value ${elements[idx][1]}, got ${value}`);
|
|
++idx;
|
|
}
|
|
is(idx, 3, `AsyncIterableDouble: Should have 3 loops of for-await-of, got ${idx}`);
|
|
}
|
|
|
|
async function test_data_double_union() {
|
|
info(`AsyncIterableDoubleUnion: Testing simple iterable creation and functionality`);
|
|
|
|
// eslint-disable-next-line no-undef
|
|
let itr = new TestInterfaceAsyncIterableDoubleUnion();
|
|
is(itr.entries, itr[Symbol.asyncIterator],
|
|
`AsyncIterableDoubleUnion: Should be using @@asyncIterator for 'entries'`);
|
|
|
|
let elements = [["long", 1], ["string", "a"]];
|
|
let key_itr = itr.keys();
|
|
let value_itr = itr.values();
|
|
let entries_itr = itr.entries();
|
|
let key = await key_itr.next();
|
|
let value = await value_itr.next();
|
|
let entry = await entries_itr.next();
|
|
for (let i = 0; i < 2; ++i) {
|
|
is(key.value, elements[i][0], `AsyncIterableDoubleUnion: Key.value should be ${elements[i][0]}, got ${key.value}`);
|
|
is(key.done, false, `AsyncIterableDoubleUnion: Key.done should be false, got ${key.done}`);
|
|
is(value.value, elements[i][1], `AsyncIterableDoubleUnion: Value.value should be ${elements[i][1]}, got ${value.value}`);
|
|
is(value.done, false, `AsyncIterableDoubleUnion: Value.done should be false, got ${value.done}`);
|
|
is(entry.value[0], elements[i][0], `AsyncIterableDoubleUnion: Entry.value[0] should be ${elements[i][0]}, got ${entry.value[0]}`);
|
|
is(entry.value[1], elements[i][1], `AsyncIterableDoubleUnion: Entry.value[1] should be ${elements[i][1]}, got ${entry.value[1]}`);
|
|
is(entry.done, false, `AsyncIterableDoubleUnion: Entry.done should be false, got ${entry.done}`);
|
|
|
|
key = await key_itr.next();
|
|
value = await value_itr.next();
|
|
entry = await entries_itr.next();
|
|
}
|
|
is(key.value, undefined, `AsyncIterableDoubleUnion: Key.value should be ${undefined}, got ${key.value}`);
|
|
is(key.done, true, `AsyncIterableDoubleUnion: Key.done should be true, got ${key.done}`);
|
|
is(value.value, undefined, `AsyncIterableDoubleUnion: Value.value should be ${undefined}, got ${value.value}`);
|
|
is(value.done, true, `AsyncIterableDoubleUnion: Value.done should be true, got ${value.done}`);
|
|
is(entry.value, undefined, `AsyncIterableDoubleUnion: Entry.value should be ${undefined}, got ${entry.value}`);
|
|
is(entry.done, true, `AsyncIterableDoubleUnion: Entry.done should be true, got ${entry.done}`);
|
|
|
|
let idx = 0;
|
|
for await (let [itrkey, itrvalue] of itr) {
|
|
is(itrkey, elements[idx][0], `AsyncIterableDoubleUnion: Looping at ${idx} should have key ${elements[idx][0]}, got ${key}`);
|
|
is(itrvalue, elements[idx][1], `AsyncIterableDoubleUnion: Looping at ${idx} should have value ${elements[idx][1]}, got ${value}`);
|
|
++idx;
|
|
}
|
|
is(idx, 2, `AsyncIterableDoubleUnion: Should have 2 loops of for-await-of, got ${idx}`);
|
|
}
|
|
|
|
add_task(async function do_tests() {
|
|
await test_data_single();
|
|
await test_data_double();
|
|
await test_data_double_union();
|
|
});
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|