summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/dom/observable/tentative
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/dom/observable/tentative')
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-constructor.any.js35
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-drop.any.js152
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js105
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-map.any.js166
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-map.window.js40
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-take.any.js108
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-takeUntil.any.js130
7 files changed, 679 insertions, 57 deletions
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-constructor.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-constructor.any.js
index f108e902b3..2cd2ee2b66 100644
--- a/testing/web-platform/tests/dom/observable/tentative/observable-constructor.any.js
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-constructor.any.js
@@ -235,14 +235,18 @@ test(t => {
source.subscribe({
complete: () => {
- activeDuringComplete = innerSubscriber.active
- abortedDuringComplete = innerSubscriber.active
+ activeDuringComplete = innerSubscriber.active;
+ abortedDuringComplete = innerSubscriber.signal.aborted;
}
});
assert_true(activeBeforeComplete, "Subscription is active before complete");
assert_false(abortedBeforeComplete, "Subscription is not aborted before complete");
- assert_false(activeDuringComplete, "Subscription is not active during complete");
- assert_false(abortedDuringComplete, "Subscription is not aborted during complete");
+ assert_false(activeDuringComplete,
+ "Subscription becomes inactive during Subscriber#complete(), just " +
+ "before Observer#complete() callback is invoked");
+ assert_true(abortedDuringComplete,
+ "Subscription's signal is aborted during Subscriber#complete(), just " +
+ "before Observer#complete() callback is invoked");
assert_false(activeAfterComplete, "Subscription is not active after complete");
assert_true(abortedAfterComplete, "Subscription is aborted after complete");
}, "Subscription is inactive after complete()");
@@ -269,13 +273,18 @@ test(t => {
source.subscribe({
error: () => {
- activeDuringError = innerSubscriber.active
+ activeDuringError = innerSubscriber.active;
+ abortedDuringError = innerSubscriber.signal.aborted;
}
});
assert_true(activeBeforeError, "Subscription is active before error");
assert_false(abortedBeforeError, "Subscription is not aborted before error");
- assert_false(activeDuringError, "Subscription is not active during error");
- assert_false(abortedDuringError, "Subscription is not aborted during error");
+ assert_false(activeDuringError,
+ "Subscription becomes inactive during Subscriber#error(), just " +
+ "before Observer#error() callback is invoked");
+ assert_true(abortedDuringError,
+ "Subscription's signal is aborted during Subscriber#error(), just " +
+ "before Observer#error() callback is invoked");
assert_false(activeAfterError, "Subscription is not active after error");
assert_true(abortedAfterError, "Subscription is not aborted after error");
}, "Subscription is inactive after error()");
@@ -691,6 +700,18 @@ test(() => {
}, "Unsubscription lifecycle");
test(t => {
+ let innerSubscriber = null;
+ const source = new Observable(subscriber => {
+ innerSubscriber = subscriber;
+ subscriber.error('calling error()');
+ });
+
+ source.subscribe();
+ assert_equals(innerSubscriber.signal.reason, "calling error()",
+ "Reason is set correctly");
+}, "Subscriber#error() value is stored as Subscriber's AbortSignal's reason");
+
+test(t => {
const source = new Observable((subscriber) => {
let n = 0;
while (!subscriber.signal.aborted) {
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-drop.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-drop.any.js
new file mode 100644
index 0000000000..4b15fedfd3
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-drop.any.js
@@ -0,0 +1,152 @@
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.next(4);
+ subscriber.complete();
+ });
+
+ const results = [];
+
+ source.drop(2).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [3, 4, "complete"]);
+}, "drop(): Observable should skip the first n values from the source " +
+ "observable, then pass through the rest of the values and completion");
+
+test(() => {
+ const error = new Error('source error');
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.next(4);
+ subscriber.error(error);
+ });
+
+ const results = [];
+
+ source.drop(2).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [3, 4, error]);
+}, "drop(): Observable passes through errors from source Observable");
+
+test(() => {
+ const error = new Error('source error');
+ const source = new Observable(subscriber => {
+ subscriber.error(error);
+ subscriber.next(1);
+ });
+
+ const results = [];
+
+ source.drop(2).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [error]);
+}, "drop(): Observable passes through errors from source observable even " +
+ "before drop count is met");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.complete();
+ });
+
+ const results = [];
+
+ source.drop(2).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, ["complete"]);
+}, "drop(): Observable passes through completions from source observable even " +
+ "before drop count is met");
+
+test(() => {
+ let sourceTeardownCalled = false;
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => sourceTeardownCalled = true);
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.next(4);
+ subscriber.next(5);
+ subscriber.complete();
+ });
+
+ const results = [];
+
+ const controller = new AbortController();
+
+ source.drop(2).subscribe({
+ next: v => {
+ results.push(v);
+ if (v === 3) {
+ controller.abort();
+ }
+ },
+ error: (e) => results.push(e),
+ complete: () => results.push("complete"),
+ }, {signal: controller.signal});
+
+ assert_true(sourceTeardownCalled,
+ "Aborting outer observable unsubscribes the source observable");
+ assert_array_equals(results, [3]);
+}, "drop(): Unsubscribing from the Observable returned by drop() also " +
+ "unsubscribes from the source Observable");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const results = [];
+
+ source.drop(0).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [1, 2, 3, "complete"],
+ "Source Observable is mirrored");
+}, "drop(): A drop amount of 0 simply mirrors the source Observable");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const results = [];
+
+ // Passing `-1` here is subject to the Web IDL integer conversion semantics,
+ // which converts the drop amount to the maximum of `18446744073709551615`.
+ source.drop(-1).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, ["complete"], "Source Observable is mirrored");
+}, "drop(): Passing negative value wraps to maximum value ");
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js
new file mode 100644
index 0000000000..8a49bcf467
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js
@@ -0,0 +1,105 @@
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.next(4);
+ subscriber.complete();
+ });
+
+ const results = [];
+
+ source
+ .filter(value => value % 2 === 0)
+ .subscribe({
+ next: v => results.push(v),
+ error: () => results.push("error"),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [2, 4, "complete"]);
+}, "filter(): Returned Observable filters out results based on predicate");
+
+test(() => {
+ const error = new Error("error while filtering");
+ const results = [];
+ let teardownCalled = false;
+
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => teardownCalled = true);
+ subscriber.next(1);
+ assert_true(teardownCalled, "Teardown called once map unsubscribes due to error");
+ assert_false(subscriber.active, "Unsubscription makes Subscriber inactive");
+ subscriber.next(2);
+ subscriber.complete();
+ });
+
+ source
+ .filter(() => {
+ throw error;
+ })
+ .subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [error]);
+}, "filter(): Errors thrown in filter predicate are emitted to Observer error() handler");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.complete();
+ subscriber.next(2);
+ });
+
+ let predicateCalls = 0;
+ const results = [];
+ source.filter(v => ++predicateCalls).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push('complete'),
+ });
+
+ assert_equals(predicateCalls, 1, "Predicate is not called after complete()");
+ assert_array_equals(results, [1, "complete"]);
+}, "filter(): Passes complete() through from source Observable");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.error('error');
+ subscriber.next(2);
+ });
+
+ let predicateCalls = 0;
+ const results = [];
+ source.map(v => ++predicateCalls).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push('complete'),
+ });
+
+ assert_equals(predicateCalls, 1, "Predicate is not called after error()");
+ assert_array_equals(results, [1, "error"]);
+}, "filter(): Passes error() through from source Observable");
+
+test(() => {
+ const results = [];
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => results.push('source teardown'));
+ subscriber.signal.addEventListener('abort',
+ () => results.push('source abort event'));
+
+ subscriber.complete();
+ });
+
+ source.filter(() => results.push('filter predicate called')).subscribe({
+ complete: () => results.push('filter observable complete'),
+ });
+
+ assert_array_equals(results,
+ ['source teardown', 'source abort event', 'filter observable complete']);
+}, "filter(): Upon source completion, source Observable teardown sequence " +
+ "happens after downstream filter complete() is called");
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-map.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-map.any.js
new file mode 100644
index 0000000000..275505fb5d
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-map.any.js
@@ -0,0 +1,166 @@
+test(() => {
+ const results = [];
+ const indices = [];
+ const source = new Observable((subscriber) => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const mapped = source.map((value, i) => {
+ indices.push(i);
+ return value * 2;
+ });
+
+ assert_true(mapped instanceof Observable, "map() returns an Observable");
+
+ assert_array_equals(results, [], "Does not map until subscribed (values)");
+ assert_array_equals(indices, [], "Does not map until subscribed (indices)");
+
+ mapped.subscribe({
+ next: (value) => results.push(value),
+ error: () => results.push('error'),
+ complete: () => results.push('complete'),
+ });
+
+ assert_array_equals(results, [2, 4, 6, 'complete']);
+ assert_array_equals(indices, [0, 1, 2]);
+}, "map(): Maps values correctly");
+
+test(() => {
+ const error = new Error("error");
+ const results = [];
+ let teardownCalled = false;
+
+ const source = new Observable((subscriber) => {
+ subscriber.addTeardown(() => teardownCalled = true);
+
+ subscriber.next(1);
+ assert_false(teardownCalled,
+ "Teardown not called until until map unsubscribes due to error");
+ subscriber.next(2);
+ assert_true(teardownCalled, "Teardown called once map unsubscribes due to error");
+ assert_false(subscriber.active, "Unsubscription makes Subscriber inactive");
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const mapped = source.map((value) => {
+ if (value === 2) {
+ throw error;
+ }
+ return value * 2;
+ });
+
+ mapped.subscribe({
+ next: (value) => results.push(value),
+ error: (error) => results.push(error),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [2, error],
+ "Mapper errors are emitted to Observer error() handler");
+}, "map(): Mapper errors are emitted to Observer error() handler");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.complete();
+ subscriber.next(2);
+ });
+
+ let mapperCalls = 0;
+ const results = [];
+ source.map(v => {
+ mapperCalls++;
+ return v * 2;
+ }).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push('complete'),
+ });
+
+ assert_equals(mapperCalls, 1, "Mapper is not called after complete()");
+ assert_array_equals(results, [2, "complete"]);
+}, "map(): Passes complete() through from source Observable");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.error('error');
+ subscriber.next(2);
+ });
+
+ let mapperCalls = 0;
+ const results = [];
+ source.map(v => {
+ mapperCalls++;
+ return v * 2;
+ }).subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push('complete'),
+ });
+
+ assert_equals(mapperCalls, 1, "Mapper is not called after error()");
+ assert_array_equals(results, [2, "error"]);
+}, "map(): Passes error() through from source Observable");
+
+// This is mostly ensuring that the ordering in
+// https://wicg.github.io/observable/#dom-subscriber-complete is consistent.
+//
+// That is, the `Subscriber#complete()` method *first* closes itself and signals
+// abort on its own `Subscriber#signal()` and *then* calls whatever supplied
+// completion algorithm exists. In the case of `map()`, the "supplied completion
+// algorithm" is simply a set of internal observer steps that call
+// `Subscriber#complete()` on the *outer* mapper's Observer. This means the
+// outer Observer is notified of completion *after* the source Subscriber's
+// signal is aborted / torn down.
+test(() => {
+ const results = [];
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => results.push('source teardown'));
+ subscriber.signal.addEventListener('abort',
+ () => results.push('source abort event'));
+
+ subscriber.complete();
+ });
+
+ source.map(() => results.push('mapper called')).subscribe({
+ complete: () => results.push('map observable complete'),
+ });
+
+ assert_array_equals(results,
+ ['source teardown', 'source abort event', 'map observable complete']);
+}, "map(): Upon source completion, source Observable teardown sequence " +
+ "happens before downstream mapper complete() is called");
+
+test(() => {
+ const results = [];
+ let sourceSubscriber = null;
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => results.push('source teardown'));
+ sourceSubscriber = subscriber;
+
+ subscriber.next(1);
+ });
+
+ const controller = new AbortController();
+ source.map(v => v * 2).subscribe({
+ next: v => {
+ results.push(v);
+
+ // Triggers unsubscription to `source`.
+ controller.abort();
+
+ // Does nothing, since `source` is already torn down.
+ sourceSubscriber.next(100);
+ },
+ complete: () => results.push('mapper complete'),
+ error: e => results.push('mapper error'),
+ }, {signal: controller.signal});
+
+ assert_array_equals(results, [2, 'source teardown']);
+}, "map(): Map observable unsubscription causes source Observable " +
+ "unsubscription. Mapper Observer's complete()/error() are not called");
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-map.window.js b/testing/web-platform/tests/dom/observable/tentative/observable-map.window.js
new file mode 100644
index 0000000000..06bf2e26b5
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-map.window.js
@@ -0,0 +1,40 @@
+async function loadIframeAndReturnContentWindow() {
+ // Create and attach an iframe.
+ const iframe = document.createElement('iframe');
+ const iframeLoadPromise = new Promise((resolve, reject) => {
+ iframe.onload = resolve;
+ iframe.onerror = reject;
+ });
+ document.body.append(iframe);
+ await iframeLoadPromise;
+ return iframe.contentWindow;
+}
+
+promise_test(async t => {
+ const contentWin = await loadIframeAndReturnContentWindow();
+
+ window.results = [];
+
+ contentWin.eval(`
+ const parentResults = parent.results;
+
+ const source = new Observable(subscriber => {
+ // Detach the document before calling next().
+ window.frameElement.remove();
+
+ // This invokes the map() operator's internal observer's next steps,
+ // which at least in Chromium, must have a special "context is detached"
+ // check to early-return, so as to not crash before invoking the "mapper"
+ // callback supplied to the map() operator.
+ subscriber.next(1);
+ });
+
+ source.map(value => {
+ parentResults.push(value);
+ }).subscribe(v => parentResults.push(v));
+ `);
+
+ // If we got here, we didn't crash! Let's also check that `results` is empty.
+ assert_array_equals(results, []);
+}, "map()'s internal observer's next steps do not crash in a detached document");
+
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-take.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-take.any.js
new file mode 100644
index 0000000000..8350d0214c
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-take.any.js
@@ -0,0 +1,108 @@
+test(() => {
+ const results = [];
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => results.push("source teardown"));
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const result = source.take(2);
+
+ result.subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [1, 2, "source teardown", "complete"]);
+}, "take(): Takes the first N values from the source observable, then completes");
+
+test(() => {
+ const results = [];
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => results.push("source teardown"));
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const result = source.take(5);
+
+ result.subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [1, 2, 3, "source teardown", "complete"],
+ "complete() is immediately forwarded");
+}, "take(): Forwards complete()s that happen before the take count is met, " +
+ "and unsubscribes from source Observable");
+
+test(() => {
+ const results = [];
+ const error = new Error('source error');
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.error(error);
+ });
+
+ const result = source.take(100);
+
+ result.subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [1, error], "Errors are forwarded");
+}, "take(): Should forward errors from the source observable");
+
+test(() => {
+ const results = [];
+ const source = new Observable((subscriber) => {
+ results.push("source subscribe");
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const result = source.take(0);
+
+ result.subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, ["complete"]);
+}, "take(): take(0) should not subscribe to the source observable, and " +
+ "should return an observable that immediately completes");
+
+test(() => {
+ const results = [];
+ const source = new Observable((subscriber) => {
+ results.push("source subscribe");
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ // Per WebIDL, `-1` passed into an `unsigned long long` gets wrapped around
+ // into the maximum value (18446744073709551615), which means the `result`
+ // Observable captures everything that `source` does.
+ const result = source.take(-1);
+
+ result.subscribe({
+ next: v => results.push(v),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, ["source subscribe", 1, 2, 3, "complete"]);
+}, "take(): Negative count is treated as maximum value");
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-takeUntil.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-takeUntil.any.js
index 6421777e09..2895dd31e3 100644
--- a/testing/web-platform/tests/dom/observable/tentative/observable-takeUntil.any.js
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-takeUntil.any.js
@@ -32,74 +32,102 @@ promise_test(async () => {
// `takeUntil()` operator, the spec responds to `notifier`'s `next()` by
// unsubscribing from `notifier`, which is what this test asserts.
promise_test(async () => {
- const source = new Observable(subscriber => {});
+ const results = [];
+ const source = new Observable(subscriber => {
+ results.push('source subscribe callback');
+ subscriber.addTeardown(() => results.push('source teardown'));
+ });
- let notifierSubscriberActiveBeforeNext;
- let notifierSubscriberActiveAfterNext;
- let teardownCalledAfterNext;
- let notifierSignalAbortedAfterNext;
const notifier = new Observable(subscriber => {
- let teardownCalled;
- subscriber.addTeardown(() => teardownCalled = true);
+ subscriber.addTeardown(() => results.push('notifier teardown'));
+ results.push('notifier subscribe callback');
// Calling `next()` causes `takeUntil()` to unsubscribe from `notifier`.
- notifierSubscriberActiveBeforeNext = subscriber.active;
+ results.push(`notifer active before next(): ${subscriber.active}`);
subscriber.next('value');
- notifierSubscriberActiveAfterNext = subscriber.active;
- teardownCalledAfterNext = (teardownCalled === true);
- notifierSignalAbortedAfterNext = subscriber.signal.aborted;
+ results.push(`notifer active after next(): ${subscriber.active}`);
});
- let nextOrErrorCalled = false;
- let completeCalled = false;
source.takeUntil(notifier).subscribe({
- next: () => nextOrErrorCalled = true,
- error: () => nextOrErrorCalled = true,
- complete: () => completeCalled = true,
+ next: () => results.push('takeUntil() next callback'),
+ error: e => results.push(`takeUntil() error callback: ${error}`),
+ complete: () => results.push('takeUntil() complete callback'),
});
- assert_true(notifierSubscriberActiveBeforeNext);
- assert_false(notifierSubscriberActiveAfterNext);
- assert_true(teardownCalledAfterNext);
- assert_true(notifierSignalAbortedAfterNext);
- assert_false(nextOrErrorCalled);
- assert_true(completeCalled);
-}, "takeUntil: notifier next() unsubscribes to notifier");
+ assert_array_equals(results, [
+ 'notifier subscribe callback',
+ 'notifer active before next(): true',
+ 'notifier teardown',
+ 'takeUntil() complete callback',
+ 'notifer active after next(): false',
+ ]);
+}, "takeUntil: notifier next() unsubscribes from notifier");
// This test is identical to the one above, with the exception being that the
// `notifier` calls `subscriber.error()` instead `subscriber.next()`.
promise_test(async () => {
- const source = new Observable(subscriber => {});
+ const results = [];
+ const source = new Observable(subscriber => {
+ results.push('source subscribe callback');
+ subscriber.addTeardown(() => results.push('source teardown'));
+ });
- let notifierSubscriberActiveBeforeNext;
- let notifierSubscriberActiveAfterNext;
- let teardownCalledAfterNext;
- let notifierSignalAbortedAfterNext;
const notifier = new Observable(subscriber => {
- let teardownCalled;
- subscriber.addTeardown(() => teardownCalled = true);
+ subscriber.addTeardown(() => results.push('notifier teardown'));
+ results.push('notifier subscribe callback');
// Calling `next()` causes `takeUntil()` to unsubscribe from `notifier`.
- notifierSubscriberActiveBeforeNext = subscriber.active;
+ results.push(`notifer active before error(): ${subscriber.active}`);
subscriber.error('error');
- notifierSubscriberActiveAfterNext = subscriber.active;
- teardownCalledAfterNext = (teardownCalled === true);
- notifierSignalAbortedAfterNext = subscriber.signal.aborted;
+ results.push(`notifer active after error(): ${subscriber.active}`);
});
- let nextOrErrorCalled = false;
- let completeCalled = false;
source.takeUntil(notifier).subscribe({
- next: () => nextOrErrorCalled = true,
- error: () => nextOrErrorCalled = true,
- complete: () => completeCalled = true,
+ next: () => results.push('takeUntil() next callback'),
+ error: e => results.push(`takeUntil() error callback: ${error}`),
+ complete: () => results.push('takeUntil() complete callback'),
});
- assert_true(notifierSubscriberActiveBeforeNext);
- assert_false(notifierSubscriberActiveAfterNext);
- assert_true(teardownCalledAfterNext);
- assert_true(notifierSignalAbortedAfterNext);
- assert_false(nextOrErrorCalled);
- assert_true(completeCalled);
-}, "takeUntil: notifier error() unsubscribes to notifier");
+
+ assert_array_equals(results, [
+ 'notifier subscribe callback',
+ 'notifer active before error(): true',
+ 'notifier teardown',
+ 'takeUntil() complete callback',
+ 'notifer active after error(): false',
+ ]);
+}, "takeUntil: notifier error() unsubscribes from notifier");
+// This test is identical to the above except it `throw`s instead of calling
+// `Subscriber#error()`.
+promise_test(async () => {
+ const results = [];
+ const source = new Observable(subscriber => {
+ results.push('source subscribe callback');
+ subscriber.addTeardown(() => results.push('source teardown'));
+ });
+
+ const notifier = new Observable(subscriber => {
+ subscriber.addTeardown(() => results.push('notifier teardown'));
+
+ results.push('notifier subscribe callback');
+ // Calling `next()` causes `takeUntil()` to unsubscribe from `notifier`.
+ results.push(`notifer active before throw: ${subscriber.active}`);
+ throw new Error('custom error');
+ // Won't run:
+ results.push(`notifer active after throw: ${subscriber.active}`);
+ });
+
+ source.takeUntil(notifier).subscribe({
+ next: () => results.push('takeUntil() next callback'),
+ error: e => results.push(`takeUntil() error callback: ${error}`),
+ complete: () => results.push('takeUntil() complete callback'),
+ });
+
+ assert_array_equals(results, [
+ 'notifier subscribe callback',
+ 'notifer active before throw: true',
+ 'notifier teardown',
+ 'takeUntil() complete callback',
+ ]);
+}, "takeUntil: notifier throw Error unsubscribes from notifier");
// Test that `notifier` unsubscribes from source Observable.
promise_test(async t => {
@@ -130,9 +158,10 @@ promise_test(async t => {
let notifierTeardownCalledBeforeCompleteCallback;
await new Promise(resolve => {
source.takeUntil(notifier).subscribe({
- next: () => nextOrErrorCalled = true,
- error: () => nextOrErrorCalled = true,
+ next: () => {nextOrErrorCalled = true; results.push('next callback');},
+ error: () => {nextOrErrorCalled = true; results.push('error callback');},
complete: () => {
+ results.push('complete callback');
notifierTeardownCalledBeforeCompleteCallback = notifierTeardownCalled;
resolve();
},
@@ -145,7 +174,7 @@ promise_test(async t => {
// The notifier/source teardowns are not called by the time the outer
// `Observer#complete()` callback is invoked, but they are all run *after*
// (i.e., before `notifier`'s `subscriber.next()` returns internally).
- assert_false(notifierTeardownCalledBeforeCompleteCallback);
+ assert_true(notifierTeardownCalledBeforeCompleteCallback);
assert_true(notifierTeardownCalled);
assert_array_equals(results, [
"notifier subscribed",
@@ -153,7 +182,8 @@ promise_test(async t => {
"notifier teardown",
"notifier signal abort",
"source teardown",
- "source signal abort"
+ "source signal abort",
+ "complete callback",
]);
}, "takeUntil: notifier next() unsubscribes from notifier & source observable");