diff options
Diffstat (limited to '')
-rw-r--r-- | dom/bindings/test/test_async_iterable.html | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/dom/bindings/test/test_async_iterable.html b/dom/bindings/test/test_async_iterable.html new file mode 100644 index 0000000000..8f1f04aea7 --- /dev/null +++ b/dom/bindings/test/test_async_iterable.html @@ -0,0 +1,300 @@ +<!-- 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, reject) => { + 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> |