| // 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", |
| "Inner signal abort event", |
| "Teardown", |
| "Outer 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", |
| "Inner signal abort event", |
| "Teardown", |
| "Outer signal abort event", |
| "Inner signal Promise", |
| "Teardown Promise", |
| "Outer signal Promise", |
| ], "Promises resolve in the right order"); |
| }, "Operator Promise abort ordering"); |
| |