Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 6 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /dom/observable/tentative/observable-map.any.html - WPT Dashboard Interop Dashboard
- /dom/observable/tentative/observable-map.any.worker.html - WPT Dashboard Interop Dashboard
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
//
// 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 abort event', 'source teardown', '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");