blob: 37c242661d6041db1df39802768d440e53b515c8 [file] [log] [blame]
test(() => {
assert_implements(self.Observable, "The Observable interface is not implemented");
assert_true(
typeof Observable === "function",
"Observable constructor is defined"
);
assert_throws_js(TypeError, () => { new Observable(); });
}, "Observable constructor");
test(() => {
assert_implements(self.Subscriber, "The Subscriber interface is not implemented");
assert_true(
typeof Subscriber === "function",
"Subscriber interface is defined as a function"
);
assert_throws_js(TypeError, () => { new Subscriber(); });
new Observable(subscriber => {
assert_not_equals(subscriber, undefined, "A Subscriber must be passed into the subscribe callback");
assert_implements(subscriber.next, "A Subscriber object must have a next() method");
assert_implements(subscriber.complete, "A Subscriber object must have a complete() method");
assert_implements(subscriber.error, "A Subscriber object must have an error() method");
}).subscribe();
}, "Subscriber interface is not constructible");
test(() => {
let initializerCalled = false;
const source = new Observable(() => {
initializerCalled = true;
});
assert_false(
initializerCalled,
"initializer should not be called by construction"
);
source.subscribe();
assert_true(initializerCalled, "initializer should be called by subscribe");
}, "subscribe() can be called with no arguments");
test(() => {
let initializerCalled = false;
const results = [];
const source = new Observable((subscriber) => {
initializerCalled = true;
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
subscriber.complete();
});
assert_false(
initializerCalled,
"initializer should not be called by construction"
);
source.subscribe({
next: (x) => results.push(x),
error: () => assert_unreached("error should not be called"),
complete: () => results.push("complete"),
});
assert_true(initializerCalled, "initializer should be called by subscribe");
assert_array_equals(
results,
[1, 2, 3, "complete"],
"should emit values synchronously"
);
}, "Observable constructor calls initializer on subscribe");
test(() => {
const error = new Error("error");
const results = [];
const source = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(error);
});
source.subscribe({
next: () => assert_unreached("next should not be called"),
error: (e) => results.push(e),
complete: () => assert_unreached("complete should not be called"),
});
assert_array_equals(
results,
[1, 2, error],
"should emit error synchronously"
);
}, "Observable error path called synchronously");
test(() => {
const error = new Error("error");
const results = [];
const source = new Observable((subscriber) => {
subscriber.next(1);
throw error;
// TODO(https://github.com/WICG/observable/issues/76): If we add the
// `subscriber.closed` attribute, consider a try-finally block to assert
// that `subscriber.closed` is true after throwing. Also TODO: ensure that
// that would even be the right behavior.
});
source.subscribe({
next: (x) => results.push(x),
error: (e) => results.push(e),
complete: () => assert_unreached("complete should not be called"),
});
assert_array_equals(
results,
[1, error],
"should emit values and the throw error synchronously"
);
}, "Observable should error if initializer throws");
// TODO(https://github.com/WICG/observable/issues/76): If we decide the
// `subscriber.closed` attribute is needed, re-visit these two tests that were
// originally included:
// https://github.com/web-platform-tests/wpt/blob/0246526ca46ef4e5eae8b8e4a87dd905c40f5326/dom/observable/tentative/observable-ctor.any.js#L123-L137.
test(() => {
const results = [];
const source = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.complete();
subscriber.next(3);
});
source.subscribe({
next: (x) => results.push(x),
error: () => assert_unreached("error should not be called"),
complete: () => results.push("complete"),
});
assert_array_equals(
results,
[1, 2, "complete"],
"should emit values synchronously, but not nexted values after complete"
);
}, "Subscription does not emit values after completion");
test(() => {
const error = new Error("error");
const results = [];
const source = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(error);
subscriber.next(3);
});
source.subscribe({
next: (x) => results.push(x),
error: (e) => results.push(e),
complete: () => assert_unreached("complete should not be called"),
});
assert_array_equals(
results,
[1, 2, error],
"should emit values synchronously, but not nexted values after error"
);
}, "Subscription does not emit values after error");
test(() => {
const error = new Error("error");
const results = [];
const source = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.error(error);
// TODO(https://github.com/WICG/observable/issues/76): Assert
// `subscriber.closed` is true, if we add that attribute.
// assert_true(subscriber.closed, "subscriber is closed after error");
subscriber.next();
subscriber.complete();
});
source.subscribe({
next: (x) => results.push(x),
error: (error) => results.push(error),
complete: () => assert_unreached("complete should not be called"),
});
assert_array_equals(results, [1, 2, error], "should emit synchronously");
}, "Completing or nexting a subscriber after an error does nothing");
test(() => {
const error = new Error("error");
let errorReported = false;
self.addEventListener(
"error",
(e) => {
assert_equals(e.message, "Uncaught (in observable) error");
assert_equals(e.filename, location.href);
assert_greater_than(e.lineno, 0);
assert_greater_than(e.colno, 0);
assert_equals(e.error, error);
errorReported = true;
},
{ once: true }
);
const source = new Observable((subscriber) => {
subscriber.error(error);
});
// No error handler provided.
source.subscribe({
next: () => assert_unreached("next should not be called"),
complete: () => assert_unreached("complete should not be called"),
});
assert_true(errorReported);
}, "Errors pushed to the subscriber that are not handled by the subscription " +
"are reported to the global");
test(() => {
const error = new Error("error");
let errorReported = false;
self.addEventListener(
"error",
(e) => {
assert_equals(e.message, "Uncaught (in observable) error");
assert_equals(e.filename, location.href);
assert_greater_than(e.lineno, 0);
assert_greater_than(e.colno, 0);
assert_equals(e.error, error);
errorReported = true;
},
{ once: true }
);
const source = new Observable((subscriber) => {
throw error;
});
// No error handler provided.
source.subscribe({
next: () => assert_unreached("next should not be called"),
complete: () => assert_unreached("complete should not be called"),
});
assert_true(errorReported);
}, "Errors thrown in the initializer that are not handled by the " +
"subscription are reported to the global");
test(() => {
const error = new Error("error");
const results = [];
let errorReported = false;
self.addEventListener(
"error",
(e) => {
assert_equals(e.message, "Uncaught (in observable) error");
assert_equals(e.filename, location.href);
assert_greater_than(e.lineno, 0);
assert_greater_than(e.colno, 0);
assert_equals(e.error, error);
errorReported = true;
},
{ once: true }
);
const source = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.complete();
subscriber.error(error);
});
source.subscribe({
next: (x) => results.push(x),
error: () => assert_unreached("error should not be called"),
complete: () => results.push("complete"),
});
assert_array_equals(
results,
[1, 2, "complete"],
"should emit values synchronously, but not error values after complete"
);
assert_true(errorReported);
}, "Subscription reports errors that are pushed after subscriber is closed " +
"by completion");
test(() => {
const error = new Error("error");
const results = [];
let errorReported = false;
self.addEventListener(
"error",
(e) => {
assert_equals(e.message, "Uncaught (in observable) error");
assert_equals(e.filename, location.href);
assert_greater_than(e.lineno, 0);
assert_greater_than(e.colno, 0);
assert_equals(e.error, error);
errorReported = true;
},
{ once: true }
);
const source = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.complete();
throw error;
});
source.subscribe({
next: (x) => results.push(x),
error: () => assert_unreached("error should not be called"),
complete: () => results.push("complete"),
});
assert_array_equals(
results,
[1, 2, "complete"],
"should emit values synchronously, but not error after complete"
);
assert_true(errorReported);
}, "Errors thrown by initializer function after subscriber is closed by " +
"completion are reported");