diff options
Diffstat (limited to 'testing/web-platform/tests/dom/observable/tentative/observable-toArray.any.js')
-rw-r--r-- | testing/web-platform/tests/dom/observable/tentative/observable-toArray.any.js | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-toArray.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-toArray.any.js new file mode 100644 index 0000000000..9e6e3abee5 --- /dev/null +++ b/testing/web-platform/tests/dom/observable/tentative/observable-toArray.any.js @@ -0,0 +1,174 @@ +// Because we test that the global error handler is called at various times. +setup({allow_uncaught_exception: true}); + +promise_test(async () => { + const observable = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + const array = await observable.toArray(); + assert_array_equals(array, [1, 2, 3]); +}, "toArray(): basic next/complete"); + +promise_test(async t => { + let errorReported = null; + let innerSubscriber = null; + self.addEventListener('error', e => errorReported = e, {once: true}); + + const error = new Error("custom error"); + const observable = new Observable(subscriber => { + innerSubscriber = subscriber; + subscriber.error(error); + }); + + try { + const array = await observable.toArray(); + assert_unreached("toArray() promise must not resolve"); + } catch (e) { + assert_equals(e, error); + assert_equals(errorReported, null); + + // Calls to `error()` after the subscription is closed still report the + // exception. + innerSubscriber.error(error); + assert_not_equals(errorReported, null, "Exception was reported to global"); + assert_true(errorReported.message.includes("custom error"), "Error message matches"); + assert_greater_than(errorReported.lineno, 0, "Error lineno is greater than 0"); + assert_greater_than(errorReported.colno, 0, "Error lineno is greater than 0"); + assert_equals(errorReported.error, error, "Error object is equivalent"); + } +}, "toArray(): first error() rejects promise; subsequent error()s report the exceptions"); + +promise_test(async t => { + let errorReported = null; + let innerSubscriber = null; + self.addEventListener('error', e => errorReported = e, {once: true}); + + const error = new Error("custom error"); + const observable = new Observable(subscriber => { + innerSubscriber = subscriber; + subscriber.complete(); + }); + + const array = await observable.toArray(); + assert_array_equals(array, []); + assert_equals(errorReported, null); + + // Calls to `error()` after the subscription is closed still report the + // exception. + innerSubscriber.error(error); + assert_not_equals(errorReported, null, "Exception was reported to global"); + assert_true(errorReported.message.includes("custom error"), "Error message matches"); + assert_greater_than(errorReported.lineno, 0, "Error lineno is greater than 0"); + assert_greater_than(errorReported.colno, 0, "Error lineno is greater than 0"); + assert_equals(errorReported.error, error, "Error object is equivalent"); +}, "toArray(): complete() resolves promise; subsequent error()s report the exceptions"); + +promise_test(async () => { + // This tracks whether `postSubscriptionPromise` has had its then handler run. + // This helps us keep track of the timing/ordering of everything. Calling a + // Promise-returning operator with an aborted signal must *immediately* reject + // the returned Promise, which means code "awaiting" it should run before any + // subsequent Promise resolution/rejection handlers are run. + let postSubscriptionPromiseResolved = false; + let subscriptionImmediatelyInactive = false; + + const observable = new Observable(subscriber => { + const inactive = !subscriber.active; + subscriptionImmediatelyInactive = inactive; + }); + + const rejectedPromise = observable.toArray({signal: AbortSignal.abort()}) + .then(() => { + assert_unreached("Operator promise must not resolve its abort signal is " + + "rejected"); + }, () => { + // See the documentation above. The rejection handler (i.e., this code) for + // immediately-aborted operator Promises runs before any later-scheduled + // Promise resolution/rejections. + assert_false(postSubscriptionPromiseResolved, + "Operator promise rejects before later promise"); + }); + const postSubscriptionPromise = + Promise.resolve().then(() => postSubscriptionPromiseResolved = true); + + await rejectedPromise; +}, "toArray(): Subscribing with an aborted signal returns an immediately " + + "rejected promise"); + +promise_test(async () => { + let postSubscriptionPromiseResolved = false; + + const observable = new Observable(subscriber => {}); + const controller = new AbortController(); + const arrayPromise = observable.toArray({signal: controller.signal}) + .then(() => { + assert_unreached("Operator promise must not resolve if its abort signal " + + "is rejected"); + }, () => { + assert_false(postSubscriptionPromiseResolved, + "controller.abort() synchronously rejects the operator " + + "Promise"); + }); + + // This must synchronously reject `arrayPromise`, scheduling in the next + // microtask. + controller.abort(); + Promise.resolve().then(value => postSubscriptionPromiseResolved = true); + + await arrayPromise; +}, "toArray(): Aborting the passed-in signal rejects the returned promise"); + +// See https://github.com/WICG/observable/issues/96 for discussion about this. +promise_test(async () => { + const results = []; + + const observable = new Observable(subscriber => { + results.push(`Subscribed. active: ${subscriber.active}`); + + subscriber.signal.addEventListener('abort', e => { + results.push("Inner signal abort event"); + Promise.resolve("Inner signal Promise").then(value => results.push(value)); + }); + + subscriber.addTeardown(() => { + results.push("Teardown"); + Promise.resolve("Teardown Promise").then(value => results.push(value)); + }); + }); + + const controller = new AbortController(); + controller.signal.addEventListener('abort', e => { + results.push("Outer signal abort event"); + Promise.resolve("Outer signal Promise").then(value => results.push(value)); + }); + + // Subscribe. + observable.toArray({signal: controller.signal}); + controller.abort(); + + assert_array_equals(results, [ + "Subscribed. active: true", + "Outer signal abort event", + "Teardown", + "Inner signal abort event", + ], "Events and teardowns are fired in the right ordered"); + + // Everything microtask above should be queued up by now, so queue one more + // final microtask that will run after all of the others, wait for it, and the + // check `results` is right. + await Promise.resolve(); + assert_array_equals(results, [ + "Subscribed. active: true", + "Outer signal abort event", + "Teardown", + "Inner signal abort event", + "Outer signal Promise", + "Teardown Promise", + "Inner signal Promise", + ], "Promises resolve in the right order"); +}, "Operator Promise abort ordering"); + |