Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

// 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");