diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index fbe20e182068f..73091cd0ff0fa 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -7887,3 +7887,6 @@ imported/w3c/web-platform-tests/css/css-viewport/zoom/word-spacing.html [ ImageO # WebRTC Encoded Transform - Test Expectation - Crashes on 'mac-wk2' debug and gtk-wk2 / wpe-wk2 webkit.org/b/275663 imported/w3c/web-platform-tests/webrtc-encoded-transform/script-transform-generateKeyFrame-simulcast.https.html [ Skip ] + +# https://bugs.webkit.org/show_bug.cgi?id=266843 flakey tests with racey promise console.logs +imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.html [ Skip ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any-expected.txt index 566694acde1c9..393efecb1601c 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any-expected.txt @@ -8,6 +8,7 @@ CONSOLE MESSAGE: not a real error CONSOLE MESSAGE: not a real error CONSOLE MESSAGE: not a real error CONSOLE MESSAGE: Error: assert_false: expected false got true +CONSOLE MESSAGE: calling error() CONSOLE MESSAGE: Error: custom error CONSOLE MESSAGE: Error: custom error @@ -38,7 +39,11 @@ PASS Errors thrown by initializer function after subscriber is closed by error a PASS Errors pushed by initializer function after subscriber is closed by error are reported PASS Subscriber#complete() cannot re-entrantly invoke itself PASS Subscriber#error() cannot re-entrantly invoke itself -FAIL Unsubscription lifecycle assert_array_equals: expected property 1 to be "outer abort handler" but got "inner abort handler" (expected array ["subscribe() callback", "outer abort handler", "teardown 2", "teardown 1", "inner abort handler", "abort() returned"] got ["subscribe() callback", "inner abort handler", "teardown 2", "teardown 1", "outer abort handler", "abort() returned"]) +PASS Unsubscription lifecycle +PASS Teardowns are called in upstream->downstream order on consumer-initiated unsubscription +PASS Teardowns are called in downstream->upstream order on consumer-initiated unsubscription with pre-aborted Signal +PASS Producer-initiated unsubscription in a downstream Observable fires abort events before each teardown, in downstream->upstream order +FAIL Subscriber#error() value is stored as Subscriber's AbortSignal's reason assert_equals: Reason is set correctly expected (string) "calling error()" but got (object) object "AbortError: The operation was aborted." PASS Aborting a subscription should stop emitting values PASS Calling subscribe should never throw an error synchronously, initializer throws error PASS Calling subscribe should never throw an error synchronously, subscriber pushes error diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any.js index f108e902b32d0..109ed284db044 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any.js +++ b/LayoutTests/imported/w3c/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()"); @@ -679,10 +688,26 @@ test(() => { assert_array_equals(results, ['subscribe() callback']); ac.abort(); results.push('abort() returned'); + // The reason the "inner" abort event handler is invoked first is because the + // "inner" AbortSignal is not a dependent signal (that would ordinarily get + // aborted after the parent, aka "outer" signal, is completely finished being + // aborted). Instead, the order of operations looks like this: + // 1. "Outer" signal begins to be aborted + // 2. Its abort algorithms [1] run [2]; the internal abort algorithm here is + // the "inner" Subscriber's "Close a subscription" [0]. + // a. This signals abort on the "inner" Subscriber's signal, firing the + // abort event + // b. Then, the "inner" Subscriber's teardowns run. + // 3. Once the "outer" signal's abort algorithms are finished, the abort + // event is fired [3], triggering the outer abort handler. + // + // [0]: https://wicg.github.io/observable/#close-a-subscription + // [1]: https://dom.spec.whatwg.org/#abortsignal-abort-algorithms + // [2]: https://dom.spec.whatwg.org/#ref-for-abortsignal-abort-algorithms%E2%91%A2:~:text=For%20each%20algorithm%20of%20signal%E2%80%99s%20abort%20algorithms%3A%20run%20algorithm + // [3]: https://dom.spec.whatwg.org/#abortsignal-signal-abort:~:text=Fire%20an%20event%20named%20abort%20at%20signal assert_array_equals(results, [ - 'subscribe() callback', - 'outer abort handler', 'teardown 2', 'teardown 1', - 'inner abort handler', 'abort() returned', + 'subscribe() callback', 'inner abort handler', 'teardown 2', 'teardown 1', + 'outer abort handler', 'abort() returned', ]); assert_false(activeDuringTeardown1, 'should not be active during teardown callback 1'); assert_false(activeDuringTeardown2, 'should not be active during teardown callback 2'); @@ -690,6 +715,139 @@ test(() => { assert_true(abortedDuringTeardown2, 'should be aborted during teardown callback 2'); }, "Unsubscription lifecycle"); +// In the usual consumer-initiated unsubscription case, when the AbortController +// is aborted after subscription, teardowns run from upstream->downstream. This +// is because for a given Subscriber, when a downstream signal is aborted +// (`ac.signal` in this case), the "Close" algorithm prompts the Subscriber to +// first abort *its* own signal (the one accessible via `Subscriber#signal`) and +// then run its teardowns. +// +// This means upstream Subscribers get the first opportunity their teardowns +// before the control flow is returned to downstream Subscribers to run *their* +// teardowns (after they abort their internal signal). +test(() => { + const results = []; + const upstream = new Observable(subscriber => { + subscriber.signal.addEventListener('abort', + e => results.push('upstream abort handler'), {once: true}); + subscriber.addTeardown( + () => results.push(`upstream teardown. reason: ${subscriber.signal.reason}`)); + }); + const middle = new Observable(subscriber => { + subscriber.signal.addEventListener('abort', + e => results.push('middle abort handler'), {once: true}); + subscriber.addTeardown( + () => results.push(`middle teardown. reason: ${subscriber.signal.reason}`)); + upstream.subscribe({}, {signal: subscriber.signal}); + }); + const downstream = new Observable(subscriber => { + subscriber.signal.addEventListener('abort', + e => results.push('downstream abort handler'), {once: true}); + subscriber.addTeardown( + () => results.push(`downstream teardown. reason: ${subscriber.signal.reason}`)); + middle.subscribe({}, {signal: subscriber.signal}); + }); + + const ac = new AbortController(); + downstream.subscribe({}, {signal: ac.signal}); + ac.abort('Abort!'); + assert_array_equals(results, [ + 'upstream abort handler', + 'upstream teardown. reason: Abort!', + 'middle abort handler', + 'middle teardown. reason: Abort!', + 'downstream abort handler', + 'downstream teardown. reason: Abort!', + ]); +}, "Teardowns are called in upstream->downstream order on " + + "consumer-initiated unsubscription"); + +// This test is like the above, but asserts the exact opposite order of +// teardowns. This is because, since the Subscriber's signal is aborted +// immediately upon construction, `addTeardown()` runs teardowns synchronously +// in subscriber-order, which goes from downstream->upstream. +test(() => { + const results = []; + const upstream = new Observable(subscriber => { + subscriber.addTeardown( + () => results.push(`upstream teardown. reason: ${subscriber.signal.reason}`)); + }); + const middle = new Observable(subscriber => { + subscriber.addTeardown( + () => results.push(`middle teardown. reason: ${subscriber.signal.reason}`)); + upstream.subscribe({}, {signal: subscriber.signal}); + }); + const downstream = new Observable(subscriber => { + subscriber.addTeardown( + () => results.push(`downstream teardown. reason: ${subscriber.signal.reason}`)); + middle.subscribe({}, {signal: subscriber.signal}); + }); + + downstream.subscribe({}, {signal: AbortSignal.abort('Initial abort')}); + assert_array_equals(results, [ + "downstream teardown. reason: Initial abort", + "middle teardown. reason: Initial abort", + "upstream teardown. reason: Initial abort", + ]); +}, "Teardowns are called in downstream->upstream order on " + + "consumer-initiated unsubscription with pre-aborted Signal"); + +// Producer-initiated unsubscription test, capturing the ordering of abort events and teardowns. +test(() => { + const results = []; + + const source = new Observable(subscriber => { + subscriber.addTeardown(() => results.push('source teardown')); + subscriber.signal.addEventListener('abort', + e => results.push('source abort event')); + }); + + const middle = new Observable(subscriber => { + subscriber.addTeardown(() => results.push('middle teardown')); + subscriber.signal.addEventListener('abort', + e => results.push('middle abort event')); + + source.subscribe(() => {}, {signal: subscriber.signal}); + }); + + let innerSubscriber = null; + const downstream = new Observable(subscriber => { + innerSubscriber = subscriber; + subscriber.addTeardown(() => results.push('downstream teardown')); + subscriber.signal.addEventListener('abort', + e => results.push('downstream abort event')); + + middle.subscribe(() => {}, {signal: subscriber.signal}); + }); + + downstream.subscribe(); + + // Trigger a producer-initiated unsubscription from the most-downstream Observable. + innerSubscriber.complete(); + + assert_array_equals(results, [ + 'source abort event', + 'source teardown', + 'middle abort event', + 'middle teardown', + 'downstream abort event', + 'downstream teardown', + ]); +}, "Producer-initiated unsubscription in a downstream Observable fires abort " + + "events before each teardown, in downstream->upstream order"); + +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; diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any.worker-expected.txt index f892c48bde1dc..375d6182f0d8e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any.worker-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any.worker-expected.txt @@ -26,7 +26,11 @@ PASS Errors thrown by initializer function after subscriber is closed by error a PASS Errors pushed by initializer function after subscriber is closed by error are reported PASS Subscriber#complete() cannot re-entrantly invoke itself PASS Subscriber#error() cannot re-entrantly invoke itself -FAIL Unsubscription lifecycle assert_array_equals: expected property 1 to be "outer abort handler" but got "inner abort handler" (expected array ["subscribe() callback", "outer abort handler", "teardown 2", "teardown 1", "inner abort handler", "abort() returned"] got ["subscribe() callback", "inner abort handler", "teardown 2", "teardown 1", "outer abort handler", "abort() returned"]) +PASS Unsubscription lifecycle +PASS Teardowns are called in upstream->downstream order on consumer-initiated unsubscription +PASS Teardowns are called in downstream->upstream order on consumer-initiated unsubscription with pre-aborted Signal +PASS Producer-initiated unsubscription in a downstream Observable fires abort events before each teardown, in downstream->upstream order +FAIL Subscriber#error() value is stored as Subscriber's AbortSignal's reason assert_equals: Reason is set correctly expected (string) "calling error()" but got (object) object "AbortError: The operation was aborted." PASS Aborting a subscription should stop emitting values PASS Calling subscribe should never throw an error synchronously, initializer throws error PASS Calling subscribe should never throw an error synchronously, subscriber pushes error diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any-expected.txt new file mode 100644 index 0000000000000..d2c9905cef48e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any-expected.txt @@ -0,0 +1,9 @@ + +FAIL drop(): Observable should skip the first n values from the source observable, then pass through the rest of the values and completion source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): Observable passes through errors from source Observable source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): Observable passes through errors from source observable even before drop count is met source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): Observable passes through completions from source observable even before drop count is met source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): Unsubscribing from the Observable returned by drop() also unsubscribes from the source Observable source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): A drop amount of 0 simply mirrors the source Observable source.drop is not a function. (In 'source.drop(0)', 'source.drop' is undefined) +FAIL drop(): Passing negative value wraps to maximum value source.drop is not a function. (In 'source.drop(-1)', 'source.drop' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.js new file mode 100644 index 0000000000000..4b15fedfd33b3 --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.worker-expected.txt new file mode 100644 index 0000000000000..d2c9905cef48e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.worker-expected.txt @@ -0,0 +1,9 @@ + +FAIL drop(): Observable should skip the first n values from the source observable, then pass through the rest of the values and completion source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): Observable passes through errors from source Observable source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): Observable passes through errors from source observable even before drop count is met source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): Observable passes through completions from source observable even before drop count is met source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): Unsubscribing from the Observable returned by drop() also unsubscribes from the source Observable source.drop is not a function. (In 'source.drop(2)', 'source.drop' is undefined) +FAIL drop(): A drop amount of 0 simply mirrors the source Observable source.drop is not a function. (In 'source.drop(0)', 'source.drop' is undefined) +FAIL drop(): Passing negative value wraps to maximum value source.drop is not a function. (In 'source.drop(-1)', 'source.drop' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any-expected.txt new file mode 100644 index 0000000000000..a15bab01da3d0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any-expected.txt @@ -0,0 +1,24 @@ + +FAIL every(): Promise resolves to true if all values pass the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value) => value === "good")', 'source.every' is undefined)" +FAIL every(): Promise resolves to false if any value fails the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value) => value === "good")', 'source.every' is undefined)" +FAIL every(): Abort the subscription to the source if the predicate does not pass promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value) => value === "good")', 'source.every' is undefined)" +FAIL every(): Lifecycle checks when all values pass the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value, index) => { + logs.push(`Predicate called with ${value}, ${index}`); + return true; + })', 'source.every' is undefined)" +FAIL every(): Lifecycle checks when any value fails the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value, index) => { + logs.push(`Predicate called with ${value}, ${index}`); + return value === "good"; + })', 'source.every' is undefined)" +FAIL every(): Resolves with true if the observable completes without emitting a value promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every(() => true)', 'source.every' is undefined)" +FAIL every(): Rejects with any error emitted from the source observable promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every(() => true)', 'source.every' is undefined)" +FAIL every(): Rejects with any error thrown from the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every(value => { + if (value <= 2) return true; + throw error; + })', 'source.every' is undefined)" +FAIL every(): Index is passed into the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value, index) => { + indices.push(index); + return true; + })', 'source.every' is undefined)" +FAIL every(): Rejects with a DOMException if the source Observable is aborted promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every(() => true, { signal: controller.signal })', 'source.every' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.js new file mode 100644 index 0000000000000..74a344b8f7855 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.js @@ -0,0 +1,250 @@ +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.next("good"); + subscriber.next("good"); + subscriber.next("good"); + subscriber.complete(); + }); + + const result = await source.every((value) => value === "good"); + + assert_true(result, "Promise resolves with true if all values pass the predicate"); +}, "every(): Promise resolves to true if all values pass the predicate"); + +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.next("good"); + subscriber.next("good"); + subscriber.next("bad"); + subscriber.complete(); + }); + + const result = await source.every((value) => value === "good"); + + assert_false(result, "Promise resolves with false if any value fails the predicate"); +}, "every(): Promise resolves to false if any value fails the predicate"); + +promise_test(async () => { + let tornDown = false; + let subscriberActiveAfterFailingPredicate = true; + const source = new Observable(subscriber => { + subscriber.addTeardown(() => tornDown = true); + subscriber.next("good"); + subscriber.next("good"); + subscriber.next("bad"); + subscriberActiveAfterFailingPredicate = subscriber.active; + subscriber.next("good"); + subscriber.complete(); + }); + + const result = await source.every((value) => value === "good"); + + assert_false(result, "Promise resolves with false if any value fails the predicate"); + assert_false(subscriberActiveAfterFailingPredicate, + "Subscriber becomes inactive because every() unsubscribed"); +}, "every(): Abort the subscription to the source if the predicate does not pass"); + +promise_test(async () => { + const logs = []; + + const source = createTestSubject({ + onSubscribe: () => logs.push("subscribed to source"), + onTeardown: () => logs.push("teardown"), + }); + + const resultPromise = source.every((value, index) => { + logs.push(`Predicate called with ${value}, ${index}`); + return true; + }); + + let promiseResolved = false; + + resultPromise.then(() => promiseResolved = true); + + assert_array_equals(logs, ["subscribed to source"], + "calling every() subscribes to the source immediately"); + + source.next("a"); + assert_array_equals(logs, [ + "subscribed to source", + "Predicate called with a, 0" + ], "Predicate called with the value and the index"); + + source.next("b"); + assert_array_equals(logs, [ + "subscribed to source", + "Predicate called with a, 0", + "Predicate called with b, 1", + ], "Predicate called with the value and the index"); + + // wait a tick, just to prove that you have to wait for complete to be called. + await Promise.resolve(); + + assert_false(promiseResolved, + "Promise should not resolve until after the source completes"); + + source.complete(); + assert_array_equals(logs, [ + "subscribed to source", + "Predicate called with a, 0", + "Predicate called with b, 1", + "teardown", + ], "Teardown function called immediately after the source completes"); + + const result = await resultPromise; + + assert_true(result, + "Promise resolves with true if all values pass the predicate"); +}, "every(): Lifecycle checks when all values pass the predicate"); + +promise_test(async () => { + const logs = []; + + const source = createTestSubject({ + onSubscribe: () => logs.push("subscribed to source"), + onTeardown: () => logs.push("teardown"), + }); + + const resultPromise = source.every((value, index) => { + logs.push(`Predicate called with ${value}, ${index}`); + return value === "good"; + }); + + let promiseResolved = false; + + resultPromise.then(() => promiseResolved = true); + + assert_array_equals(logs, ["subscribed to source"], + "calling every() subscribes to the source immediately"); + + source.next("good"); + source.next("good"); + assert_array_equals(logs, [ + "subscribed to source", + "Predicate called with good, 0", + "Predicate called with good, 1", + ], "Predicate called with the value and the index"); + + assert_false(promiseResolved, "Promise should not resolve until after the predicate fails"); + + source.next("bad"); + assert_array_equals(logs, [ + "subscribed to source", + "Predicate called with good, 0", + "Predicate called with good, 1", + "Predicate called with bad, 2", + "teardown", + ], "Predicate called with the value and the index, failing predicate immediately aborts subscription to source"); + + const result = await resultPromise; + + assert_false(result, "Promise resolves with false if any value fails the predicate"); +}, "every(): Lifecycle checks when any value fails the predicate"); + +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.complete(); + }); + + const result = await source.every(() => true); + + assert_true(result, + "Promise resolves with true if the observable completes without " + + "emitting a value"); +}, "every(): Resolves with true if the observable completes without " + + "emitting a value"); + +promise_test(async t => { + const error = new Error("error from source"); + const source = new Observable(subscriber => { + subscriber.error(error); + }); + + promise_rejects_exactly(t, error, source.every(() => true), + "Promise rejects with the error emitted from the source observable"); +}, "every(): Rejects with any error emitted from the source observable"); + +promise_test(async t => { + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + const error = new Error("bad value"); + const promise = source.every(value => { + if (value <= 2) return true; + throw error; + }); + + promise_rejects_exactly(t, error, promise, "Promise rejects with the " + + "error thrown from the predicate"); +}, "every(): Rejects with any error thrown from the predicate"); + +promise_test(async () => { + const indices = []; + + const source = new Observable(subscriber => { + subscriber.next("a"); + subscriber.next("b"); + subscriber.next("c"); + subscriber.complete(); + }); + + const value = await source.every((value, index) => { + indices.push(index); + return true; + }); + + assert_array_equals(indices, [0, 1, 2]); + + assert_true(value, + "Promise resolves with true if all values pass the predicate"); +}, "every(): Index is passed into the predicate"); + +promise_test(async t => { + const source = new Observable(subscriber => {}); + + const controller = new AbortController(); + const promise = source.every(() => true, { signal: controller.signal }); + controller.abort(); + + promise_rejects_dom(t, 'AbortError', promise, "Promise rejects with a " + + "DOMException if the source Observable is aborted"); +}, "every(): Rejects with a DOMException if the source Observable is aborted"); + +function createTestSubject(options) { + const onTeardown = options?.onTeardown; + + const subscribers = new Set(); + const subject = new Observable(subscriber => { + options?.onSubscribe?.(); + subscribers.add(subscriber); + subscriber.addTeardown(() => subscribers.delete(subscriber)); + if (onTeardown) { + subscriber.addTeardown(onTeardown); + } + }); + + subject.next = (value) => { + for (const subscriber of Array.from(subscribers)) { + subscriber.next(value); + } + }; + subject.error = (error) => { + for (const subscriber of Array.from(subscribers)) { + subscriber.error(error); + } + }; + subject.complete = () => { + for (const subscriber of Array.from(subscribers)) { + subscriber.complete(); + } + }; + subject.subscriberCount = () => { + return subscribers.size; + }; + + return subject; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.worker-expected.txt new file mode 100644 index 0000000000000..a15bab01da3d0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.worker-expected.txt @@ -0,0 +1,24 @@ + +FAIL every(): Promise resolves to true if all values pass the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value) => value === "good")', 'source.every' is undefined)" +FAIL every(): Promise resolves to false if any value fails the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value) => value === "good")', 'source.every' is undefined)" +FAIL every(): Abort the subscription to the source if the predicate does not pass promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value) => value === "good")', 'source.every' is undefined)" +FAIL every(): Lifecycle checks when all values pass the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value, index) => { + logs.push(`Predicate called with ${value}, ${index}`); + return true; + })', 'source.every' is undefined)" +FAIL every(): Lifecycle checks when any value fails the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value, index) => { + logs.push(`Predicate called with ${value}, ${index}`); + return value === "good"; + })', 'source.every' is undefined)" +FAIL every(): Resolves with true if the observable completes without emitting a value promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every(() => true)', 'source.every' is undefined)" +FAIL every(): Rejects with any error emitted from the source observable promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every(() => true)', 'source.every' is undefined)" +FAIL every(): Rejects with any error thrown from the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every(value => { + if (value <= 2) return true; + throw error; + })', 'source.every' is undefined)" +FAIL every(): Index is passed into the predicate promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every((value, index) => { + indices.push(index); + return true; + })', 'source.every' is undefined)" +FAIL every(): Rejects with a DOMException if the source Observable is aborted promise_test: Unhandled rejection with value: object "TypeError: source.every is not a function. (In 'source.every(() => true, { signal: controller.signal })', 'source.every' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any-expected.txt new file mode 100644 index 0000000000000..3863d3d25cd3a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any-expected.txt @@ -0,0 +1,16 @@ + +FAIL filter(): Returned Observable filters out results based on predicate source + .filter is not a function. (In 'source + .filter(value => value % 2 === 0)', 'source + .filter' is undefined) +FAIL filter(): Errors thrown in filter predicate are emitted to Observer error() handler source + .filter is not a function. (In 'source + .filter(() => { + throw error; + })', 'source + .filter' is undefined) +FAIL filter(): Passes complete() through from source Observable source.filter is not a function. (In 'source.filter(v => ++predicateCalls)', 'source.filter' is undefined) +FAIL filter(): Passes error() through from source Observable source.map is not a function. (In 'source.map(v => ++predicateCalls)', 'source.map' is undefined) +FAIL filter(): Upon source completion, source Observable teardown sequence happens after downstream filter complete() is called source.filter is not a function. (In 'source.filter(() => results.push('filter predicate called'))', 'source.filter' is undefined) +FAIL filter(): Index is passed correctly to predicate source.filter is not a function. (In 'source.filter((value, index) => indices.push(index))', 'source.filter' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.js new file mode 100644 index 0000000000000..419d59ed8a8c4 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.js @@ -0,0 +1,118 @@ +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"); + results.push(subscriber.signal.reason); + 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, 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 abort event', 'source teardown', 'filter observable complete']); +}, "filter(): Upon source completion, source Observable teardown sequence " + + "happens after downstream filter complete() is called"); + +test(() => { + const source = new Observable(subscriber => { + subscriber.next('value1'); + subscriber.next('value2'); + subscriber.next('value3'); + }); + + const indices = []; + source.filter((value, index) => indices.push(index)).subscribe(); + assert_array_equals(indices, [0, 1, 2]); +}, "filter(): Index is passed correctly to predicate"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.worker-expected.txt new file mode 100644 index 0000000000000..3863d3d25cd3a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.worker-expected.txt @@ -0,0 +1,16 @@ + +FAIL filter(): Returned Observable filters out results based on predicate source + .filter is not a function. (In 'source + .filter(value => value % 2 === 0)', 'source + .filter' is undefined) +FAIL filter(): Errors thrown in filter predicate are emitted to Observer error() handler source + .filter is not a function. (In 'source + .filter(() => { + throw error; + })', 'source + .filter' is undefined) +FAIL filter(): Passes complete() through from source Observable source.filter is not a function. (In 'source.filter(v => ++predicateCalls)', 'source.filter' is undefined) +FAIL filter(): Passes error() through from source Observable source.map is not a function. (In 'source.map(v => ++predicateCalls)', 'source.map' is undefined) +FAIL filter(): Upon source completion, source Observable teardown sequence happens after downstream filter complete() is called source.filter is not a function. (In 'source.filter(() => results.push('filter predicate called'))', 'source.filter' is undefined) +FAIL filter(): Index is passed correctly to predicate source.filter is not a function. (In 'source.filter((value, index) => indices.push(index))', 'source.filter' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any-expected.txt new file mode 100644 index 0000000000000..b63e99e2fd031 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any-expected.txt @@ -0,0 +1,11 @@ + +FAIL find(): Promise resolves with the first value that passes the predicate promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find((value) => value === "b")', 'source.find' is undefined)" +FAIL find(): Promise resolves with undefined if no value passes the predicate promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find(() => false)', 'source.find' is undefined)" +FAIL find(): Promise rejects with the error emitted from the source Observable promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find(() => true)', 'source.find' is undefined)" +FAIL find(): Promise rejects with any error thrown from the predicate promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find(() => {throw error})', 'source.find' is undefined)" +FAIL find(): Passes the index of the value to the predicate promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find((value, index) => { + indices.push(index); + return false; + })', 'source.find' is undefined)" +FAIL find(): Rejects with AbortError when the signal is aborted promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find(() => true, { signal: controller.signal })', 'source.find' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.js new file mode 100644 index 0000000000000..0e09060fc5ac1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.js @@ -0,0 +1,85 @@ +promise_test(async () => { + let inactiveAfterB = false; + const source = new Observable(subscriber => { + subscriber.next("a"); + subscriber.next("b"); + inactiveAfterB = !subscriber.active; + subscriber.next("c"); + subscriber.complete(); + }); + + const value = await source.find((value) => value === "b"); + + assert_equals(value, "b", "Promise resolves with the first value that passes the predicate"); + + assert_true(inactiveAfterB, "subscriber is inactive after the first value that passes the predicate"); +}, "find(): Promise resolves with the first value that passes the predicate"); + +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.next("a"); + subscriber.next("b"); + subscriber.next("c"); + subscriber.complete(); + }); + + const value = await source.find(() => false); + + assert_equals(value, undefined, "Promise resolves with undefined if no value passes the predicate"); +}, "find(): Promise resolves with undefined if no value passes the predicate"); + +promise_test(async t => { + const error = new Error("error from source"); + const source = new Observable(subscriber => { + subscriber.error(error); + }); + + promise_rejects_exactly(t, error, source.find(() => true), "Promise " + + "rejects with the error emitted from the source Observable"); +}, "find(): Promise rejects with the error emitted from the source Observable"); + +promise_test(async t => { + const source = new Observable(subscriber => { + subscriber.next("ignored"); + }); + + const error = new Error("thrown from predicate"); + promise_rejects_exactly(t, error, source.find(() => {throw error}), + "Promise rejects with any error thrown from the predicate"); +}, "find(): Promise rejects with any error thrown from the predicate"); + +promise_test(async () => { + let indices = []; + + const source = new Observable(subscriber => { + subscriber.next("a"); + subscriber.next("b"); + subscriber.next("c"); + subscriber.complete(); + }); + + const value = await source.find((value, index) => { + indices.push(index); + return false; + }); + + assert_equals(value, undefined, "Promise resolves with undefined if no value passes the predicate"); + + assert_array_equals(indices, [0, 1, 2], "find(): Passes the index of the value to the predicate"); +}, "find(): Passes the index of the value to the predicate"); + +promise_test(async t => { + const controller = new AbortController(); + const source = new Observable(subscriber => { + subscriber.next("a"); + subscriber.next("b"); + subscriber.next("c"); + subscriber.complete(); + }); + + controller.abort(); + const promise = source.find(() => true, { signal: controller.signal }); + + promise_rejects_dom(t, 'AbortError', promise, "Promise rejects with " + + "DOMException when the signal is aborted"); +}, "find(): Rejects with AbortError when the signal is aborted"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.worker-expected.txt new file mode 100644 index 0000000000000..b63e99e2fd031 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.worker-expected.txt @@ -0,0 +1,11 @@ + +FAIL find(): Promise resolves with the first value that passes the predicate promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find((value) => value === "b")', 'source.find' is undefined)" +FAIL find(): Promise resolves with undefined if no value passes the predicate promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find(() => false)', 'source.find' is undefined)" +FAIL find(): Promise rejects with the error emitted from the source Observable promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find(() => true)', 'source.find' is undefined)" +FAIL find(): Promise rejects with any error thrown from the predicate promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find(() => {throw error})', 'source.find' is undefined)" +FAIL find(): Passes the index of the value to the predicate promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find((value, index) => { + indices.push(index); + return false; + })', 'source.find' is undefined)" +FAIL find(): Rejects with AbortError when the signal is aborted promise_test: Unhandled rejection with value: object "TypeError: source.find is not a function. (In 'source.find(() => true, { signal: controller.signal })', 'source.find' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any-expected.txt new file mode 100644 index 0000000000000..33f214e2d683b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any-expected.txt @@ -0,0 +1,7 @@ + +FAIL first(): Promise resolves with the first value from the source Observable promise_test: Unhandled rejection with value: object "TypeError: source.first is not a function. (In 'source.first()', 'source.first' is undefined)" +FAIL first(): Promise rejects with the error emitted from the source Observable assert_equals: Promise rejects with source Observable error expected object "Error: error from source" but got object "TypeError: source.first is not a function. (In 'source.first()', 'source.first' is undefined)" +FAIL first(): Promise rejects with RangeError when source Observable completes without emitting any values assert_true: Upon complete(), first() Promise rejects with RangeError expected true got false +FAIL first(): Aborting a signal rejects the Promise with an AbortError DOMException promise_test: Unhandled rejection with value: object "TypeError: source.first is not a function. (In 'source.first({ signal: controller.signal })', 'source.first' is undefined)" +FAIL first(): Lifecycle promise_test: Unhandled rejection with value: object "TypeError: source.first is not a function. (In 'source.first()', 'source.first' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.js new file mode 100644 index 0000000000000..d4738d7478b1e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.js @@ -0,0 +1,114 @@ +promise_test(async () => { + const results = []; + + const source = new Observable(subscriber => { + subscriber.addTeardown(() => results.push('teardown')); + subscriber.next(1); + results.push(subscriber.active ? 'active' : 'inactive'); + results.push(subscriber.signal.aborted ? 'aborted' : 'not aborted') + + // Ignored. + subscriber.next(2); + subscriber.complete(); + }); + + const value = await source.first(); + + assert_array_equals(results, ['teardown', 'inactive', 'aborted']); + assert_equals(value, 1, + "Promise resolves with the first value from the source Observable"); +}, "first(): Promise resolves with the first value from the source Observable"); + +promise_test(async () => { + const error = new Error("error from source"); + const source = new Observable(subscriber => { + subscriber.error(error); + }); + + let rejection; + try { + await source.first(); + } catch (e) { + rejection = e; + } + + assert_equals(rejection, error, "Promise rejects with source Observable error"); +}, "first(): Promise rejects with the error emitted from the source Observable"); + +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.complete(); + }); + + let rejection; + try { + await source.first(); + } catch (e) { + rejection = e; + } + + assert_true(rejection instanceof RangeError, + "Upon complete(), first() Promise rejects with RangeError"); + assert_equals(rejection.message, "No values in Observable"); +}, "first(): Promise rejects with RangeError when source Observable " + + "completes without emitting any values"); + +promise_test(async () => { + const source = new Observable(subscriber => {}); + + const controller = new AbortController(); + const promise = source.first({ signal: controller.signal }); + + controller.abort(); + + let rejection; + try { + await promise; + } catch (e) { + rejection = e; + } + + assert_true(rejection instanceof DOMException, + "Promise rejects with a DOMException for abortion"); + assert_equals(rejection.name, "AbortError", + "Rejected with 'AbortError' DOMException"); + assert_equals(rejection.message, "signal is aborted without reason"); +}, "first(): Aborting a signal rejects the Promise with an AbortError DOMException"); + +promise_test(async () => { + const results = []; + + const source = new Observable(subscriber => { + results.push("source subscribe"); + subscriber.addTeardown(() => results.push("source teardown")); + subscriber.signal.addEventListener("abort", () => results.push("source abort")); + results.push("before source next 1"); + subscriber.next(1); + results.push("after source next 1"); + }); + + results.push("calling first"); + const promise = source.first(); + + assert_array_equals(results, [ + "calling first", + "source subscribe", + "before source next 1", + "source abort", + "source teardown", + "after source next 1" + ], "Array values after first() is called"); + + const firstValue = await promise; + results.push(`first resolved with: ${firstValue}`); + + assert_array_equals(results, [ + "calling first", + "source subscribe", + "before source next 1", + "source abort", + "source teardown", + "after source next 1", + "first resolved with: 1", + ], "Array values after Promise is awaited"); +}, "first(): Lifecycle"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.worker-expected.txt new file mode 100644 index 0000000000000..33f214e2d683b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.worker-expected.txt @@ -0,0 +1,7 @@ + +FAIL first(): Promise resolves with the first value from the source Observable promise_test: Unhandled rejection with value: object "TypeError: source.first is not a function. (In 'source.first()', 'source.first' is undefined)" +FAIL first(): Promise rejects with the error emitted from the source Observable assert_equals: Promise rejects with source Observable error expected object "Error: error from source" but got object "TypeError: source.first is not a function. (In 'source.first()', 'source.first' is undefined)" +FAIL first(): Promise rejects with RangeError when source Observable completes without emitting any values assert_true: Upon complete(), first() Promise rejects with RangeError expected true got false +FAIL first(): Aborting a signal rejects the Promise with an AbortError DOMException promise_test: Unhandled rejection with value: object "TypeError: source.first is not a function. (In 'source.first({ signal: controller.signal })', 'source.first' is undefined)" +FAIL first(): Lifecycle promise_test: Unhandled rejection with value: object "TypeError: source.first is not a function. (In 'source.first()', 'source.first' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any-expected.txt new file mode 100644 index 0000000000000..717de43d97cf5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any-expected.txt @@ -0,0 +1,53 @@ + +FAIL flatMap(): Flattens simple source Observable properly source.flatMap is not a function. (In 'source.flatMap(value => { + projectionCalls++; + return new Observable((subscriber) => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + })', 'source.flatMap' is undefined) +FAIL flatMap(): Returned Observable passes through source Observable errors source.flatMap is not a function. (In 'source.flatMap(value => { + return new Observable(subscriber => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + })', 'source.flatMap' is undefined) +FAIL flatMap(): Outer Subscription synchronously becomes inactive when an 'inner' Observable emits an error source.flatMap is not a function. (In 'source.flatMap((value) => { + return new Observable((subscriber) => { + subscriber.next(value * 10); + subscriber.next(value * 100); + if (value === 2) { + subscriber.error(error); + } else { + subscriber.complete(); + } + }); + })', 'source.flatMap' is undefined) +FAIL flatMap(): Outer Subscription synchronously becomes inactive when an 'inner' Observable throws an error source.flatMap is not a function. (In 'source.flatMap(value => { + if (value === 3) { + throw error; + } + return new Observable(subscriber => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + })', 'source.flatMap' is undefined) +FAIL flatMap(): result Observable does not complete until source and inner Observables all complete source.flatMap is not a function. (In 'source.flatMap(value => { + if (value === 1) { + return inner1; + } + + return inner2; + })', 'source.flatMap' is undefined) +FAIL flatMap(): result Observable does not complete after source Observable completes while there are still queued inner Observables to process Observables all complete source.flatMap is not a function. (In 'source.flatMap(value => { + if (value === 1) { + return inner1; + } + + return inner2; + })', 'source.flatMap' is undefined) +FAIL flatMap(): source and inner active Observables are both unsubscribed from once the outer subscription signal is aborted source.flatMap is not a function. (In 'source.flatMap(() => inner)', 'source.flatMap' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.js new file mode 100644 index 0000000000000..7cbfa6cb60439 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.js @@ -0,0 +1,315 @@ +test(() => { + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + let projectionCalls = 0; + + const results = []; + + const flattened = source.flatMap(value => { + projectionCalls++; + return new Observable((subscriber) => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + }); + + assert_true(flattened instanceof Observable, "flatMap() returns an Observable"); + assert_equals(projectionCalls, 0, + "Projection is not called until subscription starts"); + + flattened.subscribe({ + next: v => results.push(v), + error: () => results.push("error"), + complete: () => results.push("complete"), + }); + + assert_equals(projectionCalls, 3, + "Mapper is called three times, once for each source Observable value"); + assert_array_equals(results, [10, 100, 20, 200, 30, 300, "complete"], + "flatMap() results are correct"); +}, "flatMap(): Flattens simple source Observable properly"); + +test(() => { + const error = new Error("error"); + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.error(error); + subscriber.next(3); + }); + + const flattened = source.flatMap(value => { + return new Observable(subscriber => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + }); + + const results = []; + + flattened.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, [10, 100, 20, 200, error], + "Source error is passed through to the flatMap() Observable"); +}, "flatMap(): Returned Observable passes through source Observable errors"); + +test(() => { + const results = []; + const error = new Error("error"); + const source = new Observable(subscriber => { + subscriber.next(1); + results.push(subscriber.active ? "active" : "inactive"); + subscriber.next(2); + results.push(subscriber.active ? "active" : "inactive"); + subscriber.next(3); + subscriber.complete(); + }); + + const flattened = source.flatMap((value) => { + return new Observable((subscriber) => { + subscriber.next(value * 10); + subscriber.next(value * 100); + if (value === 2) { + subscriber.error(error); + } else { + subscriber.complete(); + } + }); + }); + + flattened.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, [10, 100, "active", 20, 200, error, "inactive"], + "Inner subscription error gets surfaced"); +}, "flatMap(): Outer Subscription synchronously becomes inactive when an " + + "'inner' Observable emits an error"); + +test(() => { + const results = []; + const error = new Error("error"); + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + results.push(subscriber.active ? "active" : "inactive"); + subscriber.complete(); + }); + + const flattened = source.flatMap(value => { + if (value === 3) { + throw error; + } + return new Observable(subscriber => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + }); + + flattened.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, [10, 100, 20, 200, error, "inactive"], + "Inner subscriber thrown error gets surfaced"); +}, "flatMap(): Outer Subscription synchronously becomes inactive when an " + + "'inner' Observable throws an error"); + +test(() => { + const source = createTestSubject(); + const inner1 = createTestSubject(); + const inner2 = createTestSubject(); + + const flattened = source.flatMap(value => { + if (value === 1) { + return inner1; + } + + return inner2; + }); + + const results = []; + + flattened.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, []); + + source.next(1); + assert_equals(inner1.subscriberCount(), 1, "inner1 gets subscribed to"); + + source.next(2); + assert_equals(inner2.subscriberCount(), 0, + "inner2 is queued, not subscribed to until inner1 completes"); + + assert_array_equals(results, []); + + inner1.next(100); + inner1.next(101); + + assert_array_equals(results, [100, 101]); + + inner1.complete(); + assert_equals(inner1.subscriberCount(), 0, + "inner1 becomes inactive once it completes"); + assert_equals(inner2.subscriberCount(), 1, + "inner2 gets un-queued and subscribed to once inner1 completes"); + + inner2.next(200); + inner2.next(201); + assert_array_equals(results, [100, 101, 200, 201]); + + inner2.complete(); + assert_equals(inner2.subscriberCount(), 0, + "inner2 becomes inactive once it completes"); + assert_equals(source.subscriberCount(), 1, + "source is not unsubscribed from yet, since it has not completed"); + assert_array_equals(results, [100, 101, 200, 201]); + + source.complete(); + assert_equals(source.subscriberCount(), 0, + "source unsubscribed from after it completes"); + + assert_array_equals(results, [100, 101, 200, 201, "complete"]); +}, "flatMap(): result Observable does not complete until source and inner " + + "Observables all complete"); + +test(() => { + const source = createTestSubject(); + const inner1 = createTestSubject(); + const inner2 = createTestSubject(); + + const flattened = source.flatMap(value => { + if (value === 1) { + return inner1; + } + + return inner2; + }); + + const results = []; + + flattened.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, []); + + source.next(1); + source.next(2); + assert_equals(inner1.subscriberCount(), 1, "inner1 gets subscribed to"); + assert_equals(inner2.subscriberCount(), 0, + "inner2 is queued, not subscribed to until inner1 completes"); + + assert_array_equals(results, []); + + // Before `inner1` pushes any values, we first complete the source Observable. + // This will not fire completion of the Observable returned from `flatMap()`, + // because there are two values (corresponding to inner Observables) that are + // queued to the inner queue that need to be processed first. Once the last + // one of *those* completes (i.e., `inner2.complete()` further down), then the + // returned Observable can finally complete. + source.complete(); + assert_equals(source.subscriberCount(), 0, + "source becomes inactive once it completes"); + + inner1.next(100); + inner1.next(101); + + assert_array_equals(results, [100, 101]); + + inner1.complete(); + assert_array_equals(results, [100, 101], + "Outer completion not triggered after inner1 completes"); + assert_equals(inner2.subscriberCount(), 1, + "inner2 gets un-queued and subscribed after inner1 completes"); + + inner2.next(200); + inner2.next(201); + assert_array_equals(results, [100, 101, 200, 201]); + + inner2.complete(); + assert_equals(inner2.subscriberCount(), 0, + "inner2 becomes inactive once it completes"); + assert_array_equals(results, [100, 101, 200, 201, "complete"]); +}, "flatMap(): result Observable does not complete after source Observable " + + "completes while there are still queued inner Observables to process " + + "Observables all complete"); + +test(() => { + const source = createTestSubject(); + const inner = createTestSubject(); + const result = source.flatMap(() => inner); + + const ac = new AbortController(); + + result.subscribe({}, { signal: ac.signal, }); + + source.next(1); + + assert_equals(inner.subscriberCount(), 1, + "inner Observable subscribed to once source emits it"); + + ac.abort(); + + assert_equals(source.subscriberCount(), 0, + "source unsubscribed from, once outer signal is aborted"); + + assert_equals(inner.subscriberCount(), 0, + "inner Observable unsubscribed from once the outer Observable is " + + "subscribed from, as a result of the outer signal being aborted"); +}, "flatMap(): source and inner active Observables are both unsubscribed " + + "from once the outer subscription signal is aborted"); + +// A helper function to create an Observable that can be externally controlled +// and examined for testing purposes. +function createTestSubject() { + const subscribers = new Set(); + const subject = new Observable(subscriber => { + subscribers.add(subscriber); + subscriber.addTeardown(() => subscribers.delete(subscriber)); + }); + + subject.next = value => { + for (const subscriber of Array.from(subscribers)) { + subscriber.next(value); + } + }; + subject.error = error => { + for (const subscriber of Array.from(subscribers)) { + subscriber.error(error); + } + }; + subject.complete = () => { + for (const subscriber of Array.from(subscribers)) { + subscriber.complete(); + } + }; + subject.subscriberCount = () => { + return subscribers.size; + }; + + return subject; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.worker-expected.txt new file mode 100644 index 0000000000000..717de43d97cf5 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.worker-expected.txt @@ -0,0 +1,53 @@ + +FAIL flatMap(): Flattens simple source Observable properly source.flatMap is not a function. (In 'source.flatMap(value => { + projectionCalls++; + return new Observable((subscriber) => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + })', 'source.flatMap' is undefined) +FAIL flatMap(): Returned Observable passes through source Observable errors source.flatMap is not a function. (In 'source.flatMap(value => { + return new Observable(subscriber => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + })', 'source.flatMap' is undefined) +FAIL flatMap(): Outer Subscription synchronously becomes inactive when an 'inner' Observable emits an error source.flatMap is not a function. (In 'source.flatMap((value) => { + return new Observable((subscriber) => { + subscriber.next(value * 10); + subscriber.next(value * 100); + if (value === 2) { + subscriber.error(error); + } else { + subscriber.complete(); + } + }); + })', 'source.flatMap' is undefined) +FAIL flatMap(): Outer Subscription synchronously becomes inactive when an 'inner' Observable throws an error source.flatMap is not a function. (In 'source.flatMap(value => { + if (value === 3) { + throw error; + } + return new Observable(subscriber => { + subscriber.next(value * 10); + subscriber.next(value * 100); + subscriber.complete(); + }); + })', 'source.flatMap' is undefined) +FAIL flatMap(): result Observable does not complete until source and inner Observables all complete source.flatMap is not a function. (In 'source.flatMap(value => { + if (value === 1) { + return inner1; + } + + return inner2; + })', 'source.flatMap' is undefined) +FAIL flatMap(): result Observable does not complete after source Observable completes while there are still queued inner Observables to process Observables all complete source.flatMap is not a function. (In 'source.flatMap(value => { + if (value === 1) { + return inner1; + } + + return inner2; + })', 'source.flatMap' is undefined) +FAIL flatMap(): source and inner active Observables are both unsubscribed from once the outer subscription signal is aborted source.flatMap is not a function. (In 'source.flatMap(() => inner)', 'source.flatMap' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any-expected.txt new file mode 100644 index 0000000000000..8da9b48d1e033 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any-expected.txt @@ -0,0 +1,21 @@ + +FAIL forEach(): Visitor callback called synchronously for each value promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach((value) => { + results.push(value); + })', 'source.forEach' is undefined)" +FAIL Errors thrown by Observable reject the returned promise assert_equals: expected object "Error: error" but got object "TypeError: source.forEach is not a function. (In 'source.forEach(() => { + assert_unreached("Visitor callback is not invoked when Observable errors"); + })', 'source.forEach' is undefined)" +FAIL Errors pushed by Observable reject the returned promise assert_equals: expected object "Error: error" but got object "TypeError: source.forEach is not a function. (In 'source.forEach(() => { + assert_unreached("Visitor callback is not invoked when Observable errors"); + })', 'source.forEach' is undefined)" +FAIL Errors thrown in the visitor callback reject the promise and unsubscribe from the source promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach((value) => { + results.push(value); + if (value === 2) { + throw error; + } + })', 'source.forEach' is undefined)" +FAIL forEach visitor callback rejection microtask ordering promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach(() => {}, {signal: controller.signal})', 'source.forEach' is undefined)" +FAIL forEach() promise resolves with undefined promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach((value) => { + results.push(value); + })', 'source.forEach' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.js new file mode 100644 index 0000000000000..d0948b295e8d1 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.js @@ -0,0 +1,184 @@ +promise_test(async (t) => { + const source = new Observable((subscriber) => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + const results = []; + + const completion = source.forEach((value) => { + results.push(value); + }); + + assert_array_equals(results, [1, 2, 3]); + await completion; +}, "forEach(): Visitor callback called synchronously for each value"); + +promise_test(async (t) => { + const error = new Error("error"); + const source = new Observable((subscriber) => { + throw error; + }); + + try { + await source.forEach(() => { + assert_unreached("Visitor callback is not invoked when Observable errors"); + }); + assert_unreached("forEach() promise does not resolve when Observable errors"); + } catch (e) { + assert_equals(e, error); + } +}, "Errors thrown by Observable reject the returned promise"); + +promise_test(async (t) => { + const error = new Error("error"); + const source = new Observable((subscriber) => { + subscriber.error(error); + }); + + try { + await source.forEach(() => { + assert_unreached("Visitor callback is not invoked when Observable errors"); + }); + assert_unreached("forEach() promise does not resolve when Observable errors"); + } catch (reason) { + assert_equals(reason, error); + } +}, "Errors pushed by Observable reject the returned promise"); + +promise_test(async (t) => { + // This will be assigned when `source`'s teardown is called during + // unsubscription. + let abortReason = null; + + const error = new Error("error"); + const source = new Observable((subscriber) => { + // Should be called from within the second `next()` call below, when the + // `forEach()` visitor callback throws an error, because that triggers + // unsubscription from `source`. + subscriber.addTeardown(() => abortReason = subscriber.signal.reason); + + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + const results = []; + + const completion = source.forEach((value) => { + results.push(value); + if (value === 2) { + throw error; + } + }); + + assert_array_equals(results, [1, 2]); + assert_equals(abortReason, error, + "forEach() visitor callback throwing an error triggers unsubscription " + + "from the source observable, with the correct abort reason"); + + try { + await completion; + assert_unreached("forEach() promise does not resolve when visitor throws"); + } catch (e) { + assert_equals(e, error); + } +}, "Errors thrown in the visitor callback reject the promise and " + + "unsubscribe from the source"); + +// See https://github.com/WICG/observable/issues/96 for discussion about the +// timing of Observable AbortSignal `abort` firing and promise rejection. +promise_test(async t => { + const error = new Error('custom error'); + let rejectionError = null; + let outerAbortEventMicrotaskRun = false, + forEachPromiseRejectionMicrotaskRun = false, + innerAbortEventMicrotaskRun = false; + + const source = new Observable(subscriber => { + subscriber.signal.addEventListener('abort', () => { + queueMicrotask(() => { + assert_true(outerAbortEventMicrotaskRun, + "Inner abort: outer abort microtask has fired"); + assert_true(forEachPromiseRejectionMicrotaskRun, + "Inner abort: forEach rejection microtask has fired"); + assert_false(innerAbortEventMicrotaskRun, + "Inner abort: inner abort microtask has not fired"); + + innerAbortEventMicrotaskRun = true; + }); + }); + }); + + const controller = new AbortController(); + controller.signal.addEventListener('abort', () => { + queueMicrotask(() => { + assert_false(outerAbortEventMicrotaskRun, + "Outer abort: outer abort microtask has not fired"); + assert_false(forEachPromiseRejectionMicrotaskRun, + "Outer abort: forEach rejection microtask has not fired"); + assert_false(innerAbortEventMicrotaskRun, + "Outer abort: inner abort microtask has not fired"); + + outerAbortEventMicrotaskRun = true; + }); + }); + + const promise = source.forEach(() => {}, {signal: controller.signal}).catch(e => { + rejectionError = e; + assert_true(outerAbortEventMicrotaskRun, + "Promise rejection: outer abort microtask has fired"); + assert_false(forEachPromiseRejectionMicrotaskRun, + "Promise rejection: forEach rejection microtask has not fired"); + assert_false(innerAbortEventMicrotaskRun, + "Promise rejection: inner abort microtask has not fired"); + + forEachPromiseRejectionMicrotaskRun = true; + }); + + // This should trigger the following, in this order: + // 1. Fire the `abort` event at the outer AbortSignal, whose handler + // manually queues a microtask. + // 2. Calls "signal abort" on the outer signal's dependent signals. This + // queues a microtask to reject the `forEach()` promise. + // 3. Fire the `abort` event at the inner AbortSignal, whose handler + // manually queues a microtask. + controller.abort(error); + + // After a single task, assert that everything has happened correctly (and + // incrementally in the right order); + await new Promise(resolve => { + t.step_timeout(resolve); + }); + assert_true(outerAbortEventMicrotaskRun, + "Final: outer abort microtask has fired"); + assert_true(forEachPromiseRejectionMicrotaskRun, + "Final: forEach rejection microtask has fired"); + assert_true(innerAbortEventMicrotaskRun, + "Final: inner abort microtask has fired"); + assert_equals(rejectionError, error, "Promise is rejected with the right " + + "value"); +}, "forEach visitor callback rejection microtask ordering"); + +promise_test(async (t) => { + const source = new Observable((subscriber) => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + const results = []; + + const completion = source.forEach((value) => { + results.push(value); + }); + + assert_array_equals(results, [1, 2, 3]); + + const completionValue = await completion; + assert_equals(completionValue, undefined, "Promise resolves with undefined"); +}, "forEach() promise resolves with undefined"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.worker-expected.txt new file mode 100644 index 0000000000000..8da9b48d1e033 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.worker-expected.txt @@ -0,0 +1,21 @@ + +FAIL forEach(): Visitor callback called synchronously for each value promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach((value) => { + results.push(value); + })', 'source.forEach' is undefined)" +FAIL Errors thrown by Observable reject the returned promise assert_equals: expected object "Error: error" but got object "TypeError: source.forEach is not a function. (In 'source.forEach(() => { + assert_unreached("Visitor callback is not invoked when Observable errors"); + })', 'source.forEach' is undefined)" +FAIL Errors pushed by Observable reject the returned promise assert_equals: expected object "Error: error" but got object "TypeError: source.forEach is not a function. (In 'source.forEach(() => { + assert_unreached("Visitor callback is not invoked when Observable errors"); + })', 'source.forEach' is undefined)" +FAIL Errors thrown in the visitor callback reject the promise and unsubscribe from the source promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach((value) => { + results.push(value); + if (value === 2) { + throw error; + } + })', 'source.forEach' is undefined)" +FAIL forEach visitor callback rejection microtask ordering promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach(() => {}, {signal: controller.signal})', 'source.forEach' is undefined)" +FAIL forEach() promise resolves with undefined promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach((value) => { + results.push(value); + })', 'source.forEach' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window-expected.txt new file mode 100644 index 0000000000000..55ff3ebaea02b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window-expected.txt @@ -0,0 +1,9 @@ + +FAIL forEach()'s internal observer's next steps do not crash in a detached document promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach(value => { + parentResults.push(value); + })', 'source.forEach' is undefined)" +FAIL forEach()'s internal observer's next steps do not crash when visitor callback detaches the document promise_test: Unhandled rejection with value: object "TypeError: source.forEach is not a function. (In 'source.forEach(value => { + window.frameElement.remove(); + parentResults.push(value); + })', 'source.forEach' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window.js new file mode 100644 index 0000000000000..71c2e17303938 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window.js @@ -0,0 +1,59 @@ +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 => { + window.frameElement.remove(); + + // This invokes the forEach() 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. + subscriber.next(1); + }); + + source.forEach(value => { + parentResults.push(value); + }); + `); + + // If we got here, we didn't crash! Let's also check that `results` is empty. + assert_array_equals(results, []); +}, "forEach()'s internal observer's next steps do not crash in a detached document"); + +promise_test(async t => { + const contentWin = await loadIframeAndReturnContentWindow(); + + window.results = []; + + contentWin.eval(` + const parentResults = parent.results; + + const source = new Observable(subscriber => { + subscriber.next(1); + }); + + source.forEach(value => { + window.frameElement.remove(); + parentResults.push(value); + }); + `); + + assert_array_equals(results, [1]); +}, "forEach()'s internal observer's next steps do not crash when visitor " + + "callback detaches the document"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt new file mode 100644 index 0000000000000..0ee8bac7eb31a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt @@ -0,0 +1,17 @@ +CONSOLE MESSAGE: Unhandled Promise Rejection: reason +CONSOLE MESSAGE: Unhandled Promise Rejection: custom reason + +FAIL from(): Observable.from() is a function assert_equals: Observable.from() is a function expected "function" but got "undefined" +PASS from(): Failed conversions +FAIL from(): Given an observable, it returns that exact observable target.on is not a function. (In 'target.on('custom')', 'target.on' is undefined) +FAIL from(): Given an array Observable.from is not a function. (In 'Observable.from(array)', 'Observable.from' is undefined) +FAIL from(): Iterable converts to Observable Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (one observable) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (many observables) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] next() throws error Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): Converts Promise to Observable promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Converts rejected Promise to Observable. No `unhandledrejection` event when error is handled by subscription promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Rejections not handled by subscription are reported to the global, and still not sent as an unhandledrejection event assert_not_equals: Error was reported to the global got disallowed value null +FAIL from(): Observable that implements @@iterator protocol gets converted as an Observable, not iterator Observable.from is not a function. (In 'Observable.from(observable)', 'Observable.from' is undefined) +FAIL from(): Promise that implements @@iterator protocol gets converted as an iterable, not Promise Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.js new file mode 100644 index 0000000000000..80408ddced7ef --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.js @@ -0,0 +1,354 @@ +// Because we test that the global error handler is called at various times. +setup({allow_uncaught_exception: true}); + +test(() => { + assert_equals(typeof Observable.from, "function", + "Observable.from() is a function"); +}, "from(): Observable.from() is a function"); + +test(() => { + assert_throws_js(TypeError, () => Observable.from(10), + "Number cannot convert to an Observable"); + assert_throws_js(TypeError, () => Observable.from(true), + "Boolean cannot convert to an Observable"); + assert_throws_js(TypeError, () => Observable.from("String"), + "String cannot convert to an Observable"); + assert_throws_js(TypeError, () => Observable.from({a: 10}), + "Object cannot convert to an Observable"); + assert_throws_js(TypeError, () => Observable.from(Symbol.iterator), + "Bare Symbol.iterator cannot convert to an Observable"); + assert_throws_js(TypeError, () => Observable.from(Promise), + "Promise constructor cannot convert to an Observable"); +}, "from(): Failed conversions"); + +test(() => { + const target = new EventTarget(); + const observable = target.on('custom'); + const from_observable = Observable.from(observable); + assert_equals(observable, from_observable); +}, "from(): Given an observable, it returns that exact observable"); + +test(() => { + let completeCalled = false; + const results = []; + const array = [1, 2, 3, 'a', new Date(), 15, [12]]; + const observable = Observable.from(array); + observable.subscribe({ + next: v => results.push(v), + error: e => assert_unreached('error is not called'), + complete: () => completeCalled = true + }); + + assert_array_equals(results, array); + assert_true(completeCalled); +}, "from(): Given an array"); + +test(() => { + const iterable = { + [Symbol.iterator]() { + let n = 0; + return { + next() { + n++; + if (n <= 3) { + return { value: n, done: false }; + } + return { value: undefined, done: true }; + }, + }; + }, + }; + + const observable = Observable.from(iterable); + + assert_true(observable instanceof Observable, "Observable.from() returns an Observable"); + + const results = []; + + observable.subscribe({ + next: (value) => results.push(value), + error: () => assert_unreached("should not error"), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, [1, 2, 3, "complete"], + "Subscription pushes iterable values out to Observable"); + + // A second subscription should restart iteration. + observable.subscribe({ + next: (value) => results.push(value), + error: () => assert_unreached("should not error"), + complete: () => results.push("complete2"), + }); + + assert_array_equals(results, [1, 2, 3, "complete", 1, 2, 3, "complete2"], + "Subscribing again causes another fresh iteration on an un-exhausted iterable"); +}, "from(): Iterable converts to Observable"); + +// The result of the @@iterator method of the converted object is called: +// 1. Once on conversion (to test that the value is an iterable). +// 2. Once on subscription, to re-pull the iterator implementation from the +// raw JS object that the Observable owns once synchronous iteration is +// about to begin. +test(() => { + let numTimesSymbolIteratorCalled = 0; + let numTimesNextCalled = 0; + + const iterable = { + [Symbol.iterator]() { + numTimesSymbolIteratorCalled++; + return { + next() { + numTimesNextCalled++; + return {value: undefined, done: true}; + } + }; + } + }; + + const observable = Observable.from(iterable); + + assert_equals(numTimesSymbolIteratorCalled, 1, + "Observable.from(iterable) invokes the @@iterator method getter once"); + assert_equals(numTimesNextCalled, 0, + "Iterator next() is not called until subscription"); + + // Override iterable's `[Symbol.iterator]` protocol with an error-throwing + // function. We assert that on subscription, this method (the new `@@iterator` + // implementation), is called because only the raw JS object gets stored in + // the Observable that results in conversion. This raw value must get + // re-converted to an iterable once iteration is about to start. + const customError = new Error('@@iterator override error'); + iterable[Symbol.iterator] = () => { + throw customError; + }; + + let thrownError = null; + observable.subscribe({ + error: e => thrownError = e, + }); + + assert_equals(thrownError, customError, + "Error thrown from next() is passed to the error() handler"); + + assert_equals(numTimesSymbolIteratorCalled, 1, + "Subscription re-invokes @@iterator method, which now is a different " + + "method that does *not* increment our assertion value"); + assert_equals(numTimesNextCalled, 0, "Iterator next() is never called"); +}, "from(): [Symbol.iterator] side-effects (one observable)"); + +// Similar to the above test, but with more Observables! +test(() => { + let numTimesSymbolIteratorCalled = 0; + let numTimesNextCalled = 0; + + const iterable = { + [Symbol.iterator]() { + numTimesSymbolIteratorCalled++; + return { + next() { + numTimesNextCalled++; + return {value: undefined, done: true}; + } + }; + } + }; + + const obs1 = Observable.from(iterable); + const obs2 = Observable.from(iterable); + const obs3 = Observable.from(iterable); + const obs4 = Observable.from(obs3); + + assert_equals(numTimesSymbolIteratorCalled, 3, "Observable.from(iterable) invokes the iterator method getter once"); + assert_equals(numTimesNextCalled, 0, "Iterator next() is not called until subscription"); + + iterable[Symbol.iterator] = () => { + throw new Error('Symbol.iterator override error'); + }; + + let errorCount = 0; + + const observer = {error: e => errorCount++}; + obs1.subscribe(observer); + obs2.subscribe(observer); + obs3.subscribe(observer); + obs4.subscribe(observer); + assert_equals(errorCount, 4, + "Error-throwing `@@iterator` implementation is called once per " + + "subscription"); + + assert_equals(numTimesSymbolIteratorCalled, 3, + "Subscription re-invokes the iterator method getter once"); + assert_equals(numTimesNextCalled, 0, "Iterator next() is never called"); +}, "from(): [Symbol.iterator] side-effects (many observables)"); + +test(() => { + const customError = new Error('@@iterator next() error'); + const iterable = { + [Symbol.iterator]() { + return { + next() { + throw customError; + } + }; + } + }; + + let thrownError = null; + Observable.from(iterable).subscribe({ + error: e => thrownError = e, + }); + + assert_equals(thrownError, customError, + "Error thrown from next() is passed to the error() handler"); +}, "from(): [Symbol.iterator] next() throws error"); + +promise_test(async () => { + const promise = Promise.resolve('value'); + const observable = Observable.from(promise); + + assert_true(observable instanceof Observable, "Converts to Observable"); + + const results = []; + + observable.subscribe({ + next: (value) => results.push(value), + error: () => assert_unreached("error() is not called"), + complete: () => results.push("complete()"), + }); + + assert_array_equals(results, [], "Observable does not emit synchronously"); + + await promise; + + assert_array_equals(results, ["value", "complete()"], "Observable emits and completes after Promise resolves"); +}, "from(): Converts Promise to Observable"); + +promise_test(async t => { + let unhandledRejectionHandlerCalled = false; + const unhandledRejectionHandler = () => { + unhandledRejectionHandlerCalled = true; + }; + + self.addEventListener("unhandledrejection", unhandledRejectionHandler); + t.add_cleanup(() => self.removeEventListener("unhandledrejection", unhandledRejectionHandler)); + + const promise = Promise.reject("reason"); + const observable = Observable.from(promise); + + assert_true(observable instanceof Observable, "Converts to Observable"); + + const results = []; + + observable.subscribe({ + next: (value) => assert_unreached("next() not called"), + error: (error) => results.push(error), + complete: () => assert_unreached("complete() not called"), + }); + + assert_array_equals(results, [], "Observable does not emit synchronously"); + + let catchBlockEntered = false; + try { + await promise; + } catch { + catchBlockEntered = true; + } + + assert_true(catchBlockEntered, "Catch block entered"); + assert_false(unhandledRejectionHandlerCalled, "No unhandledrejection event"); + assert_array_equals(results, ["reason"], + "Observable emits error() after Promise rejects"); +}, "from(): Converts rejected Promise to Observable. No " + + "`unhandledrejection` event when error is handled by subscription"); + +promise_test(async t => { + let unhandledRejectionHandlerCalled = false; + const unhandledRejectionHandler = () => { + unhandledRejectionHandlerCalled = true; + }; + + self.addEventListener("unhandledrejection", unhandledRejectionHandler); + t.add_cleanup(() => self.removeEventListener("unhandledrejection", unhandledRejectionHandler)); + + let errorReported = null; + self.addEventListener("error", e => errorReported = e, { once: true }); + + let catchBlockEntered = false; + try { + const promise = Promise.reject("custom reason"); + const observable = Observable.from(promise); + + observable.subscribe(); + await promise; + } catch { + catchBlockEntered = true; + } + + assert_true(catchBlockEntered, "Catch block entered"); + assert_false(unhandledRejectionHandlerCalled, + "No unhandledrejection event, because error got reported to global"); + assert_not_equals(errorReported, null, "Error was reported to the global"); + + assert_true(errorReported.message.includes("custom reason"), + "Error message matches"); + assert_equals(errorReported.lineno, 0, "Error lineno is 0"); + assert_equals(errorReported.colno, 0, "Error lineno is 0"); + assert_equals(errorReported.error, "custom reason", + "Error object is equivalent"); +}, "from(): Rejections not handled by subscription are reported to the " + + "global, and still not sent as an unhandledrejection event"); + +test(() => { + const results = []; + const observable = new Observable(subscriber => { + subscriber.next('from Observable'); + subscriber.complete(); + }); + + observable[Symbol.iterator] = () => { + results.push('Symbol.iterator() called'); + return { + next() { + return {value: 'from @@iterator', done: true}; + } + }; + }; + + Observable.from(observable).subscribe({ + next: v => results.push(v), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, ["from Observable", "complete"]); +}, "from(): Observable that implements @@iterator protocol gets converted " + + "as an Observable, not iterator"); + +test(() => { + const results = []; + const promise = new Promise(resolve => { + resolve('from Promise'); + }); + + promise[Symbol.iterator] = () => { + let done = false; + return { + next() { + if (!done) { + done = true; + return {value: 'from @@iterator', done: false}; + } else { + return {value: undefined, done: true}; + } + } + }; + }; + + Observable.from(promise).subscribe({ + next: v => results.push(v), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, ["from @@iterator", "complete"]); +}, "from(): Promise that implements @@iterator protocol gets converted as " + + "an iterable, not Promise"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.worker-expected.txt new file mode 100644 index 0000000000000..3e5881f7dec9e --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.worker-expected.txt @@ -0,0 +1,15 @@ + +FAIL from(): Observable.from() is a function assert_equals: Observable.from() is a function expected "function" but got "undefined" +PASS from(): Failed conversions +FAIL from(): Given an observable, it returns that exact observable target.on is not a function. (In 'target.on('custom')', 'target.on' is undefined) +FAIL from(): Given an array Observable.from is not a function. (In 'Observable.from(array)', 'Observable.from' is undefined) +FAIL from(): Iterable converts to Observable Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (one observable) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (many observables) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] next() throws error Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): Converts Promise to Observable promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Converts rejected Promise to Observable. No `unhandledrejection` event when error is handled by subscription promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Rejections not handled by subscription are reported to the global, and still not sent as an unhandledrejection event assert_not_equals: Error was reported to the global got disallowed value null +FAIL from(): Observable that implements @@iterator protocol gets converted as an Observable, not iterator Observable.from is not a function. (In 'Observable.from(observable)', 'Observable.from' is undefined) +FAIL from(): Promise that implements @@iterator protocol gets converted as an iterable, not Promise Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any-expected.txt new file mode 100644 index 0000000000000..064967c49a390 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any-expected.txt @@ -0,0 +1,79 @@ + +FAIL inspect(): Provides a pre-subscription subscribe callback source.inspect is not a function. (In 'source.inspect({ + subscribe: () => { + inspectSubscribeCall++; + results.push(`inspect() subscribe ${inspectSubscribeCall}`); + }, + next: (value) => results.push(`inspect() next ${value}`), + error: (e) => results.push(`inspect() error ${e.message}`), + complete: () => results.push(`inspect() complete`), + })', 'source.inspect' is undefined) +FAIL inspect(): Provides a way to tap into the values and completions of the source observable using an observer source.inspect is not a function. (In 'source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + })', 'source.inspect' is undefined) +FAIL inspect(): Error handler does not stop error from being reported to the global, when subscriber does not pass error handler source.inspect is not a function. (In 'source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + })', 'source.inspect' is undefined) +FAIL inspect(): Provides a way to tap into the values and errors of the source observable using an observer. Errors are passed through source.inspect is not a function. (In 'source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + })', 'source.inspect' is undefined) +FAIL inspect(): ObserverCallback passed in source.inspect is not a function. (In 'source.inspect(value => results.push(value))', 'source.inspect' is undefined) +FAIL inspect(): Throwing an error in the observer next handler is caught and sent to the error callback of the result observable source.inspect is not a function. (In 'source.inspect({ + next: (value) => { + if (value === 2) { + throw error; + } + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Throwing an error in the observer error handler in inspect() is caught and sent to the error callback of the result observable source.inspect is not a function. (In 'source.inspect({ + error: () => { + throw inspectError; + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Throwing an error in the observer complete handler is caught and sent to the error callback of the result observable source.inspect is not a function. (In 'source.inspect({ + complete: () => { + throw error; + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Throwing an error in the next handler function in do should be caught and sent to the error callback of the result observable source.inspect is not a function. (In 'source.inspect({ + next: (value) => { + if (value === 2) { + throw error; + } + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Errors thrown in subscribe() Inspector handler subscribe handler are caught and sent to error callback source.inspect is not a function. (In 'source.inspect({ + subscribe: () => { + throw new Error("error from do subscribe handler"); + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Provides a way to tap into the moment a source observable is unsubscribed from source.inspect is not a function. (In 'source.inspect({ + abort: (reason) => { + doUnsubscribeCall++; + results.push(`inspect() abort ${doUnsubscribeCall} ${reason}`); + }, + next: (value) => results.push(`inspect() next ${value}`), + error: (e) => results.push(`inspect() error ${e.message}`), + complete: () => results.push(`inspect() complete`), + })', 'source.inspect' is undefined) +FAIL inspect(): Inspector abort() handler is not called if the source completes before the result is unsubscribed from source.inspect is not a function. (In 'source.inspect({ + next: (value) => results.push(`inspect() next ${value}`), + complete: () => results.push(`inspect() complete`), + abort: (reason) => { + inspectUnsubscribeCall++; + results.push(`inspect() abort ${inspectUnsubscribeCall} ${reason}`); + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Errors thrown from inspect()'s abort() handler are caught and reported to the global, because the subscription is already closed by the time the handler runs source.inspect is not a function. (In 'source.inspect({ + abort: () => { + results.push('abort() handler run'); + throw new Error("error from inspect() subscribe handler"); + }, + })', 'source.inspect' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.js new file mode 100644 index 0000000000000..8aff741d2670d --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.js @@ -0,0 +1,412 @@ +// Because we test that the global error handler is called at various times. +setup({ allow_uncaught_exception: true }); + +test(() => { + const results = []; + let sourceSubscriptionCall = 0; + const source = new Observable(subscriber => { + sourceSubscriptionCall++; + results.push(`source subscribe ${sourceSubscriptionCall}`); + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + let inspectSubscribeCall = 0; + const result = source.inspect({ + subscribe: () => { + inspectSubscribeCall++; + results.push(`inspect() subscribe ${inspectSubscribeCall}`); + }, + next: (value) => results.push(`inspect() next ${value}`), + error: (e) => results.push(`inspect() error ${e.message}`), + complete: () => results.push(`inspect() complete`), + }); + + result.subscribe({ + next: (value) => results.push(`result next ${value}`), + error: (e) => results.push(`result error ${e.message}`), + complete: () => results.push(`result complete`), + }); + + result.subscribe({ + next: (value) => results.push(`result next ${value}`), + error: (e) => results.push(`result error ${e.message}`), + complete: () => results.push(`result complete`), + }); + + assert_array_equals(results, + [ + "inspect() subscribe 1", + "source subscribe 1", + "inspect() next 1", + "result next 1", + "inspect() next 2", + "result next 2", + "inspect() next 3", + "result next 3", + "inspect() complete", + "result complete", + "inspect() subscribe 2", + "source subscribe 2", + "inspect() next 1", + "result next 1", + "inspect() next 2", + "result next 2", + "inspect() next 3", + "result next 3", + "inspect() complete", + "result complete", + ]); +}, "inspect(): Provides a pre-subscription subscribe callback"); + +test(() => { + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + const results = []; + + const result = source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + result.subscribe(); + result.subscribe(); + + assert_array_equals(results, [1, 2, 3, "complete", 1, 2, 3, "complete"]); +}, "inspect(): Provides a way to tap into the values and completions of the " + + "source observable using an observer"); + +test(() => { + const error = new Error("error from source"); + const source = new Observable(subscriber => subscriber.error(error)); + + const results = []; + + const result = source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + let errorReported = null; + self.addEventListener('error', e => errorReported = e.error, {once: true}); + result.subscribe(); + + assert_array_equals(results, [error]); + assert_equals(errorReported, error, + "errorReported to global matches error from source Observable"); +}, "inspect(): Error handler does not stop error from being reported to the " + + "global, when subscriber does not pass error handler"); + +test(() => { + const error = new Error("error from source"); + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.error(error); + }); + + const results = []; + + const result = source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + const observer = { + error: e => results.push(e), + }; + result.subscribe(observer); + result.subscribe(observer); + + assert_array_equals(results, [1, 2, 3, error, error, 1, 2, 3, error, error]); +}, "inspect(): Provides a way to tap into the values and errors of the " + + "source observable using an observer. Errors are passed through"); + +test(() => { + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + const results = []; + + const result = source.inspect(value => results.push(value)); + + result.subscribe(); + result.subscribe(); + + assert_array_equals(results, [1, 2, 3, 1, 2, 3]); +}, "inspect(): ObserverCallback passed in"); + +test(() => { + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + }); + + const error = new Error("error from inspect() next handler"); + const result = source.inspect({ + next: (value) => { + if (value === 2) { + throw error; + } + }, + }); + + const results1 = []; + result.subscribe({ + next: (value) => results1.push(value), + error: (e) => results1.push(e), + complete: () => results1.push("complete"), + }); + + const results2 = []; + result.subscribe({ + next: (value) => results2.push(value), + error: (e) => results2.push(e), + complete: () => results2.push("complete"), + }); + + assert_array_equals(results1, [1, error]); + assert_array_equals(results2, [1, error]); +}, "inspect(): Throwing an error in the observer next handler is caught and " + + "sent to the error callback of the result observable"); + +test(() => { + const sourceError = new Error("error from source"); + const inspectError = new Error("error from inspect() error handler"); + + const source = new Observable(subscriber => { + subscriber.error(sourceError); + }); + + const result = source.inspect({ + error: () => { + throw inspectError; + }, + }); + + const results = []; + result.subscribe({ + next: () => results.push("next"), + error: (e) => results.push(e), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, [inspectError]); +}, "inspect(): Throwing an error in the observer error handler in " + + "inspect() is caught and sent to the error callback of the result " + + "observable"); + +test(() => { + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + const error = new Error("error from inspect() complete handler"); + const result = source.inspect({ + complete: () => { + throw error; + }, + }); + + const results = []; + result.subscribe({ + next: (value) => results.push(value), + error: (e) => results.push(e), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, [1, 2, 3, error]); +}, "inspect(): Throwing an error in the observer complete handler is caught " + + "and sent to the error callback of the result observable"); + +test(() => { + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + }); + + const error = new Error("error from inspect() next handler"); + const result = source.inspect({ + next: (value) => { + if (value === 2) { + throw error; + } + }, + }); + + const results = []; + result.subscribe({ + next: (value) => results.push(value), + error: (e) => results.push(e), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, [1, error]); +}, "inspect(): Throwing an error in the next handler function in do should " + + "be caught and sent to the error callback of the result observable"); + +test(() => { + const source = new Observable(subscriber => {}); + + const result = source.inspect({ + subscribe: () => { + throw new Error("error from do subscribe handler"); + }, + }); + + const results = []; + result.subscribe({ + next: () => results.push("next"), + error: (e) => results.push(e.message), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, ["error from do subscribe handler"]); +}, "inspect(): Errors thrown in subscribe() Inspector handler subscribe " + + "handler are caught and sent to error callback"); + +test(() => { + const results = []; + let sourceTeardownCall = 0; + const source = new Observable(subscriber => { + subscriber.addTeardown(() => { + sourceTeardownCall++; + results.push(`source teardown ${sourceTeardownCall}`); + }); + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + let doUnsubscribeCall = 0; + const result = source.inspect({ + abort: (reason) => { + doUnsubscribeCall++; + results.push(`inspect() abort ${doUnsubscribeCall} ${reason}`); + }, + next: (value) => results.push(`inspect() next ${value}`), + error: (e) => results.push(`inspect() error ${e.message}`), + complete: () => results.push(`inspect() complete`), + }); + + const controller = new AbortController(); + result.subscribe({ + next: (value) => { + results.push(`result next ${value}`); + if (value === 2) { + controller.abort("abort reason"); + } + }, + error: (e) => results.push(`result error ${e.message}`), + complete: () => results.push(`result complete`), + }, { signal: controller.signal }); + + assert_array_equals(results, [ + "inspect() next 1", + "result next 1", + "inspect() next 2", + "result next 2", + "inspect() abort 1 abort reason", + "source teardown 1", + ]); +}, "inspect(): Provides a way to tap into the moment a source observable is unsubscribed from"); + +test(() => { + const results = []; + let sourceTeardownCall = 0; + const source = new Observable(subscriber => { + subscriber.addTeardown(() => { + sourceTeardownCall++; + results.push(`source teardown ${sourceTeardownCall}`); + }); + subscriber.next(1); + subscriber.next(2); + subscriber.next(3); + subscriber.complete(); + }); + + let inspectUnsubscribeCall = 0; + const result = source.inspect({ + next: (value) => results.push(`inspect() next ${value}`), + complete: () => results.push(`inspect() complete`), + abort: (reason) => { + inspectUnsubscribeCall++; + results.push(`inspect() abort ${inspectUnsubscribeCall} ${reason}`); + }, + }); + + result.subscribe({ + next: (value) => results.push(`result next ${value}`), + complete: () => results.push(`result complete`), + }); + + assert_array_equals(results, [ + "inspect() next 1", + "result next 1", + "inspect() next 2", + "result next 2", + "inspect() next 3", + "result next 3", + "source teardown 1", + "inspect() complete", + "result complete", + ]); +}, "inspect(): Inspector abort() handler is not called if the source " + + "completes before the result is unsubscribed from"); + +test(() => { + const source = new Observable(subscriber => { + subscriber.next(1); + }); + + const results = []; + + const result = source.inspect({ + abort: () => { + results.push('abort() handler run'); + throw new Error("error from inspect() subscribe handler"); + }, + }); + + const controller = new AbortController(); + + self.on('error').take(1).subscribe(e => + results.push(e.message + ', from report exception')); + + result.subscribe({ + next: (value) => { + results.push(value); + controller.abort(); + }, + // This should not be invoked at all!! + error: (e) => results.push(e.message + ', from Observer#error()'), + complete: () => results.push("complete"), + }, {signal: controller.signal}); + + assert_array_equals(results, [1, "abort() handler run", + "Uncaught Error: error from inspect() subscribe handler, from report " + + "exception"]); +}, "inspect(): Errors thrown from inspect()'s abort() handler are caught " + + "and reported to the global, because the subscription is already closed " + + "by the time the handler runs"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.worker-expected.txt new file mode 100644 index 0000000000000..064967c49a390 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.worker-expected.txt @@ -0,0 +1,79 @@ + +FAIL inspect(): Provides a pre-subscription subscribe callback source.inspect is not a function. (In 'source.inspect({ + subscribe: () => { + inspectSubscribeCall++; + results.push(`inspect() subscribe ${inspectSubscribeCall}`); + }, + next: (value) => results.push(`inspect() next ${value}`), + error: (e) => results.push(`inspect() error ${e.message}`), + complete: () => results.push(`inspect() complete`), + })', 'source.inspect' is undefined) +FAIL inspect(): Provides a way to tap into the values and completions of the source observable using an observer source.inspect is not a function. (In 'source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + })', 'source.inspect' is undefined) +FAIL inspect(): Error handler does not stop error from being reported to the global, when subscriber does not pass error handler source.inspect is not a function. (In 'source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + })', 'source.inspect' is undefined) +FAIL inspect(): Provides a way to tap into the values and errors of the source observable using an observer. Errors are passed through source.inspect is not a function. (In 'source.inspect({ + next: value => results.push(value), + error: e => results.push(e), + complete: () => results.push("complete"), + })', 'source.inspect' is undefined) +FAIL inspect(): ObserverCallback passed in source.inspect is not a function. (In 'source.inspect(value => results.push(value))', 'source.inspect' is undefined) +FAIL inspect(): Throwing an error in the observer next handler is caught and sent to the error callback of the result observable source.inspect is not a function. (In 'source.inspect({ + next: (value) => { + if (value === 2) { + throw error; + } + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Throwing an error in the observer error handler in inspect() is caught and sent to the error callback of the result observable source.inspect is not a function. (In 'source.inspect({ + error: () => { + throw inspectError; + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Throwing an error in the observer complete handler is caught and sent to the error callback of the result observable source.inspect is not a function. (In 'source.inspect({ + complete: () => { + throw error; + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Throwing an error in the next handler function in do should be caught and sent to the error callback of the result observable source.inspect is not a function. (In 'source.inspect({ + next: (value) => { + if (value === 2) { + throw error; + } + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Errors thrown in subscribe() Inspector handler subscribe handler are caught and sent to error callback source.inspect is not a function. (In 'source.inspect({ + subscribe: () => { + throw new Error("error from do subscribe handler"); + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Provides a way to tap into the moment a source observable is unsubscribed from source.inspect is not a function. (In 'source.inspect({ + abort: (reason) => { + doUnsubscribeCall++; + results.push(`inspect() abort ${doUnsubscribeCall} ${reason}`); + }, + next: (value) => results.push(`inspect() next ${value}`), + error: (e) => results.push(`inspect() error ${e.message}`), + complete: () => results.push(`inspect() complete`), + })', 'source.inspect' is undefined) +FAIL inspect(): Inspector abort() handler is not called if the source completes before the result is unsubscribed from source.inspect is not a function. (In 'source.inspect({ + next: (value) => results.push(`inspect() next ${value}`), + complete: () => results.push(`inspect() complete`), + abort: (reason) => { + inspectUnsubscribeCall++; + results.push(`inspect() abort ${inspectUnsubscribeCall} ${reason}`); + }, + })', 'source.inspect' is undefined) +FAIL inspect(): Errors thrown from inspect()'s abort() handler are caught and reported to the global, because the subscription is already closed by the time the handler runs source.inspect is not a function. (In 'source.inspect({ + abort: () => { + results.push('abort() handler run'); + throw new Error("error from inspect() subscribe handler"); + }, + })', 'source.inspect' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any-expected.txt new file mode 100644 index 0000000000000..bd20433da1c0a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any-expected.txt @@ -0,0 +1,7 @@ + +FAIL last(): Promise resolves to last value promise_test: Unhandled rejection with value: object "TypeError: source.last is not a function. (In 'source.last()', 'source.last' is undefined)" +FAIL last(): Promise rejects with emitted error assert_equals: expected object "Error: error from source" but got object "TypeError: source.last is not a function. (In 'source.last()', 'source.last' is undefined)" +FAIL last(): Promise rejects with RangeError when source Observable completes without emitting any values assert_true: Promise rejects with RangeError expected true got false +FAIL last(): Aborting a signal rejects the Promise with an AbortError DOMException promise_test: Unhandled rejection with value: object "TypeError: source.last is not a function. (In 'source.last({ signal: controller.signal })', 'source.last' is undefined)" +FAIL last(): Lifecycle promise_test: Unhandled rejection with value: object "TypeError: source.last is not a function. (In 'source.last()', 'source.last' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.js new file mode 100644 index 0000000000000..064a781cada98 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.js @@ -0,0 +1,113 @@ +promise_test(async () => { + const source = new Observable(subscriber => { + // Never exposed to the `last()` promise. + subscriber.next(1); + + subscriber.next(2); + subscriber.complete(); + }); + + const value = await source.last(); + + assert_equals(value, 2); +}, "last(): Promise resolves to last value"); + +promise_test(async () => { + const error = new Error("error from source"); + const source = new Observable(subscriber => { + subscriber.error(error); + }); + + let rejection = null; + try { + await source.last(); + } catch (e) { + rejection = e; + } + + assert_equals(rejection, error); +}, "last(): Promise rejects with emitted error"); + +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.complete(); + }); + + let rejection = null; + try { + await source.last(); + } catch (e) { + rejection = e; + } + + assert_true(rejection instanceof RangeError, + "Promise rejects with RangeError"); + assert_equals(rejection.message, "No values in Observable"); +}, "last(): Promise rejects with RangeError when source Observable " + + "completes without emitting any values"); + +promise_test(async () => { + const source = new Observable(subscriber => {}); + + const controller = new AbortController(); + const promise = source.last({ signal: controller.signal }); + + controller.abort(); + + let rejection = null; + try { + await promise; + } catch (e) { + rejection = e; + } + + assert_true(rejection instanceof DOMException, + "Promise rejects with a DOMException for abortion"); + assert_equals(rejection.name, "AbortError", + "Rejected with 'AbortError' DOMException"); + assert_equals(rejection.message, "signal is aborted without reason"); +}, "last(): Aborting a signal rejects the Promise with an AbortError DOMException"); + +promise_test(async () => { + const results = []; + const source = new Observable(subscriber => { + results.push("source subscribe"); + subscriber.addTeardown(() => results.push("source teardown")); + subscriber.signal.addEventListener("abort", () => results.push("source abort")); + results.push("before source next 1"); + subscriber.next(1); + results.push("after source next 1"); + results.push("before source complete"); + subscriber.complete(); + results.push("after source complete"); + }); + + results.push("calling last"); + const promise = source.last(); + + assert_array_equals(results, [ + "calling last", + "source subscribe", + "before source next 1", + "after source next 1", + "before source complete", + "source abort", + "source teardown", + "after source complete", + ], "Array values after last() is called"); + + const lastValue = await promise; + results.push(`last resolved with: ${lastValue}`); + + assert_array_equals(results, [ + "calling last", + "source subscribe", + "before source next 1", + "after source next 1", + "before source complete", + "source abort", + "source teardown", + "after source complete", + "last resolved with: 1", + ], "Array values after Promise is awaited"); +}, "last(): Lifecycle"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.worker-expected.txt new file mode 100644 index 0000000000000..bd20433da1c0a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.worker-expected.txt @@ -0,0 +1,7 @@ + +FAIL last(): Promise resolves to last value promise_test: Unhandled rejection with value: object "TypeError: source.last is not a function. (In 'source.last()', 'source.last' is undefined)" +FAIL last(): Promise rejects with emitted error assert_equals: expected object "Error: error from source" but got object "TypeError: source.last is not a function. (In 'source.last()', 'source.last' is undefined)" +FAIL last(): Promise rejects with RangeError when source Observable completes without emitting any values assert_true: Promise rejects with RangeError expected true got false +FAIL last(): Aborting a signal rejects the Promise with an AbortError DOMException promise_test: Unhandled rejection with value: object "TypeError: source.last is not a function. (In 'source.last({ signal: controller.signal })', 'source.last' is undefined)" +FAIL last(): Lifecycle promise_test: Unhandled rejection with value: object "TypeError: source.last is not a function. (In 'source.last()', 'source.last' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any-expected.txt new file mode 100644 index 0000000000000..175abdf5d0859 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any-expected.txt @@ -0,0 +1,22 @@ + +FAIL map(): Maps values correctly source.map is not a function. (In 'source.map((value, i) => { + indices.push(i); + return value * 2; + })', 'source.map' is undefined) +FAIL map(): Mapper errors are emitted to Observer error() handler source.map is not a function. (In 'source.map((value) => { + if (value === 2) { + throw error; + } + return value * 2; + })', 'source.map' is undefined) +FAIL map(): Passes complete() through from source Observable source.map is not a function. (In 'source.map(v => { + mapperCalls++; + return v * 2; + })', 'source.map' is undefined) +FAIL map(): Passes error() through from source Observable source.map is not a function. (In 'source.map(v => { + mapperCalls++; + return v * 2; + })', 'source.map' is undefined) +FAIL map(): Upon source completion, source Observable teardown sequence happens before downstream mapper complete() is called source.map is not a function. (In 'source.map(() => results.push('mapper called'))', 'source.map' is undefined) +FAIL map(): Map observable unsubscription causes source Observable unsubscription. Mapper Observer's complete()/error() are not called source.map is not a function. (In 'source.map(v => v * 2)', 'source.map' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.js new file mode 100644 index 0000000000000..a61c818bc1653 --- /dev/null +++ b/LayoutTests/imported/w3c/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 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"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.worker-expected.txt new file mode 100644 index 0000000000000..175abdf5d0859 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.worker-expected.txt @@ -0,0 +1,22 @@ + +FAIL map(): Maps values correctly source.map is not a function. (In 'source.map((value, i) => { + indices.push(i); + return value * 2; + })', 'source.map' is undefined) +FAIL map(): Mapper errors are emitted to Observer error() handler source.map is not a function. (In 'source.map((value) => { + if (value === 2) { + throw error; + } + return value * 2; + })', 'source.map' is undefined) +FAIL map(): Passes complete() through from source Observable source.map is not a function. (In 'source.map(v => { + mapperCalls++; + return v * 2; + })', 'source.map' is undefined) +FAIL map(): Passes error() through from source Observable source.map is not a function. (In 'source.map(v => { + mapperCalls++; + return v * 2; + })', 'source.map' is undefined) +FAIL map(): Upon source completion, source Observable teardown sequence happens before downstream mapper complete() is called source.map is not a function. (In 'source.map(() => results.push('mapper called'))', 'source.map' is undefined) +FAIL map(): Map observable unsubscription causes source Observable unsubscription. Mapper Observer's complete()/error() are not called source.map is not a function. (In 'source.map(v => v * 2)', 'source.map' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window-expected.txt new file mode 100644 index 0000000000000..e87422433b2f7 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window-expected.txt @@ -0,0 +1,5 @@ + +FAIL map()'s internal observer's next steps do not crash in a detached document promise_test: Unhandled rejection with value: object "TypeError: source.map is not a function. (In 'source.map(value => { + parentResults.push(value); + })', 'source.map' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window.js new file mode 100644 index 0000000000000..06bf2e26b5441 --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any-expected.txt new file mode 100644 index 0000000000000..dd7b6a55237ca --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any-expected.txt @@ -0,0 +1,9 @@ + +FAIL some(): subscriber is inactive after the first value that passes the predicate, because the source was unsubscribed from promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some((value) => value === "good")', 'source.some' is undefined)" +FAIL observable-some promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some((value) => value === "good")', 'source.some' is undefined)" +FAIL observable-some 1 promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some((value) => value === "good")', 'source.some' is undefined)" +FAIL some(): The returned promise rejects with an error if the predicate errors promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some(() => {throw error})', 'source.some' is undefined)" +FAIL some(): The returned promise rejects with an error if the source observable errors promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some(() => true)', 'source.some' is undefined)" +FAIL some(): The returned promise resolves as false if the source observable completes without emitting a value promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some(() => true)', 'source.some' is undefined)" +FAIL some(): The return promise rejects with a DOMException if the signal is aborted promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some(() => true, { signal: controller.signal })', 'source.some' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.js new file mode 100644 index 0000000000000..b692610df329c --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.js @@ -0,0 +1,96 @@ +promise_test(async () => { + let inactiveAfterFirstGood = true; + + const source = new Observable(subscriber => { + subscriber.next("good"); + inactiveAfterFirstGood = !subscriber.active; + subscriber.next("good"); + subscriber.next("good"); + subscriber.complete(); + }); + + const result = await source.some((value) => value === "good"); + + assert_true(result, "Promise resolves with true if any value passes the predicate"); + + assert_true(inactiveAfterFirstGood, + "subscriber is inactive after the first value that passes the " + + "predicate, because the source was unsubscribed from"); +}, "some(): subscriber is inactive after the first value that passes the predicate, because the source was unsubscribed from"); + +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.next("bad"); + subscriber.next("bad"); + subscriber.next("bad"); + subscriber.complete(); + }); + + const result = await source.some((value) => value === "good"); + + assert_false(result, "some(): Promise resolves with false if no value passes the predicate"); +}); + +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.next("bad"); + subscriber.next("bad"); + subscriber.next("good"); + subscriber.complete(); + }); + + const result = await source.some((value) => value === "good"); + + assert_true(result, "some(): Promise resolves with true if any value passes the predicate"); +}); + +promise_test(async t => { + const source = new Observable(subscriber => { + subscriber.next("not used"); + }); + + const error = new Error("thrown from predicate"); + promise_rejects_exactly(t, error, source.some(() => {throw error}), + "The returned promise rejects with an error if the predicate errors"); +}, "some(): The returned promise rejects with an error if the predicate errors"); + +promise_test(async t => { + const error = new Error("error from source"); + const source = new Observable(subscriber => { + subscriber.error(error); + }); + + promise_rejects_exactly(t, error, source.some(() => true), + "The returned promise rejects with an error if the source observable errors"); +}, "some(): The returned promise rejects with an error if the source observable errors"); + +promise_test(async () => { + const source = new Observable(subscriber => { + subscriber.complete(); + }); + + const result = await source.some(() => true); + + assert_false(result, + "The returned promise resolves as false if the source observable " + + "completes without emitting a value"); +}, "some(): The returned promise resolves as false if the source observable " + + "completes without emitting a value"); + +promise_test(async t => { + let teardownCalled = false; + const source = new Observable(subscriber => { + subscriber.addTeardown(() => { + teardownCalled = true; + }); + }); + + const controller = new AbortController(); + const promise = source.some(() => true, { signal: controller.signal }); + + controller.abort(); + + promise_rejects_dom(t, 'AbortError', promise); + assert_true(teardownCalled, + "The teardown function is called when the signal is aborted"); +}, "some(): The return promise rejects with a DOMException if the signal is aborted"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.worker-expected.txt new file mode 100644 index 0000000000000..dd7b6a55237ca --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.worker-expected.txt @@ -0,0 +1,9 @@ + +FAIL some(): subscriber is inactive after the first value that passes the predicate, because the source was unsubscribed from promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some((value) => value === "good")', 'source.some' is undefined)" +FAIL observable-some promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some((value) => value === "good")', 'source.some' is undefined)" +FAIL observable-some 1 promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some((value) => value === "good")', 'source.some' is undefined)" +FAIL some(): The returned promise rejects with an error if the predicate errors promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some(() => {throw error})', 'source.some' is undefined)" +FAIL some(): The returned promise rejects with an error if the source observable errors promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some(() => true)', 'source.some' is undefined)" +FAIL some(): The returned promise resolves as false if the source observable completes without emitting a value promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some(() => true)', 'source.some' is undefined)" +FAIL some(): The return promise rejects with a DOMException if the signal is aborted promise_test: Unhandled rejection with value: object "TypeError: source.some is not a function. (In 'source.some(() => true, { signal: controller.signal })', 'source.some' is undefined)" + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any-expected.txt new file mode 100644 index 0000000000000..f3be2e3eb68d6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any-expected.txt @@ -0,0 +1,18 @@ + +FAIL switchMap(): result subscribes to one inner observable at a time, unsubscribing from the previous active one when a new one replaces it source.switchMap is not a function. (In 'source.switchMap((value, index) => { + if (value === 1) { + return inner1; + } + if (value === 2) { + return inner2; + } + throw new Error("invalid "); + })', 'source.switchMap' is undefined) +FAIL switchMap(): result does not complete when the source observable completes, if the inner observable is still active source.switchMap is not a function. (In 'source.switchMap(() => inner)', 'source.switchMap' is undefined) +FAIL switchMap(): result emits an error if Mapper callback throws an error source.switchMap is not a function. (In 'source.switchMap(() => { + throw e; + })', 'source.switchMap' is undefined) +FAIL switchMap(): result emits an error if the source observable emits an error source.switchMap is not a function. (In 'source.switchMap(() => inner)', 'source.switchMap' is undefined) +FAIL switchMap(): result emits an error if the inner observable emits an error source.switchMap is not a function. (In 'source.switchMap(() => inner)', 'source.switchMap' is undefined) +FAIL switchMap(): should unsubscribe in the correct order when user aborts the subscription source.switchMap is not a function. (In 'source.switchMap(() => inner)', 'source.switchMap' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.js new file mode 100644 index 0000000000000..577ce2b748cd0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.js @@ -0,0 +1,252 @@ +test(() => { + const source = createTestSubject(); + const inner1 = createTestSubject(); + const inner2 = createTestSubject(); + + const result = source.switchMap((value, index) => { + if (value === 1) { + return inner1; + } + if (value === 2) { + return inner2; + } + throw new Error("invalid "); + }); + + const results = []; + + result.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + assert_equals(source.subscriberCount(), 1, + "source observable is subscribed to"); + + source.next(1); + assert_equals(inner1.subscriberCount(), 1, + "inner1 observable is subscribed to"); + + inner1.next("1a"); + assert_array_equals(results, ["1a"]); + + inner1.next("1b"); + assert_array_equals(results, ["1a", "1b"]); + + source.next(2); + assert_equals(inner1.subscriberCount(), 0, + "inner1 observable is unsubscribed from"); + assert_equals(inner2.subscriberCount(), 1, + "inner2 observable is subscribed to"); + + inner2.next("2a"); + assert_array_equals(results, ["1a", "1b", "2a"]); + + inner2.next("2b"); + assert_array_equals(results, ["1a", "1b", "2a", "2b"]); + + inner2.complete(); + assert_array_equals(results, ["1a", "1b", "2a", "2b"]); + + source.complete(); + assert_array_equals(results, ["1a", "1b", "2a", "2b", "complete"]); +}, "switchMap(): result subscribes to one inner observable at a time, " + + "unsubscribing from the previous active one when a new one replaces it"); + +test(() => { + const source = createTestSubject(); + const inner = createTestSubject(); + + const result = source.switchMap(() => inner); + + const results = []; + + result.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + assert_equals(source.subscriberCount(), 1, + "source observable is subscribed to"); + assert_equals(inner.subscriberCount(), 0, + "inner observable is not subscribed to"); + + source.next(1); + assert_equals(inner.subscriberCount(), 1, + "inner observable is subscribed to"); + + inner.next("a"); + assert_array_equals(results, ["a"]); + + inner.next("b"); + assert_array_equals(results, ["a", "b"]); + + source.complete(); + assert_array_equals(results, ["a", "b"], + "Result observable does not complete when source observable completes, " + + "because inner is still active"); + + inner.next("c"); + assert_array_equals(results, ["a", "b", "c"]); + + inner.complete(); + assert_array_equals(results, ["a", "b", "c", "complete"], + "Result observable completes when inner observable completes, because " + + "source is already complete"); +}, "switchMap(): result does not complete when the source observable " + + "completes, if the inner observable is still active"); + +test(() => { + const source = createTestSubject(); + + const e = new Error('thrown from mapper'); + const result = source.switchMap(() => { + throw e; + }); + + const results = []; + + result.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + assert_equals(source.subscriberCount(), 1, + "source observable is subscribed to"); + + source.next(1); + assert_array_equals(results, [e]); + assert_equals(source.subscriberCount(), 0, + "source observable is unsubscribed from"); +}, "switchMap(): result emits an error if Mapper callback throws an error"); + +test(() => { + const source = createTestSubject(); + const inner = createTestSubject(); + + const result = source.switchMap(() => inner); + + const results = []; + + result.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + source.next(1); + inner.next("a"); + assert_array_equals(results, ["a"]); + + const e = new Error('error from source'); + source.error(e); + assert_array_equals(results, ["a", e], + "switchMap result emits an error if the source emits an error"); + assert_equals(inner.subscriberCount(), 0, + "inner observable is unsubscribed from"); + assert_equals(source.subscriberCount(), 0, + "source observable is unsubscribed from"); +}, "switchMap(): result emits an error if the source observable emits an " + + "error"); + +test(() => { + const source = createTestSubject(); + const inner = createTestSubject(); + + const result = source.switchMap(() => inner); + + const results = []; + + result.subscribe({ + next: v => results.push(v), + error: e => results.push(e), + complete: () => results.push("complete"), + }); + + source.next(1); + inner.next("a"); + assert_array_equals(results, ["a"]); + + const e = new Error("error from inner"); + inner.error(e); + assert_array_equals(results, ["a", e], + "result emits an error if the inner observable emits an error"); + assert_equals(inner.subscriberCount(), 0, + "inner observable is unsubscribed from"); + assert_equals(source.subscriberCount(), 0, + "source observable is unsubscribed from"); +}, "switchMap(): result emits an error if the inner observable emits an error"); + +test(() => { + const results = []; + const source = new Observable(subscriber => { + subscriber.next(1); + subscriber.addTeardown(() => { + results.push('source teardown'); + }); + subscriber.signal.onabort = e => { + results.push('source onabort'); + }; + }); + + const inner = new Observable(subscriber => { + subscriber.addTeardown(() => { + results.push('inner teardown'); + }); + subscriber.signal.onabort = () => { + results.push('inner onabort'); + }; + }); + + const result = source.switchMap(() => inner); + + const ac = new AbortController(); + result.subscribe({ + next: v => results.push(v), + error: e => results.error(e), + complete: () => results.complete("complete"), + }, {signal: ac.signal}); + + ac.abort(); + assert_array_equals(results, [ + "source onabort", + "source teardown", + "inner onabort", + "inner teardown", + ], "Unsubscription order is correct"); +}, "switchMap(): should unsubscribe in the correct order when user aborts " + + "the subscription"); + +// A helper function to create an Observable that can be externally controlled +// and examined for testing purposes. +function createTestSubject() { + const subscribers = new Set(); + const subject = new Observable(subscriber => { + subscribers.add(subscriber); + subscriber.addTeardown(() => subscribers.delete(subscriber)); + }); + + subject.next = value => { + for (const subscriber of Array.from(subscribers)) { + subscriber.next(value); + } + }; + subject.error = error => { + for (const subscriber of Array.from(subscribers)) { + subscriber.error(error); + } + }; + subject.complete = () => { + for (const subscriber of Array.from(subscribers)) { + subscriber.complete(); + } + }; + subject.subscriberCount = () => { + return subscribers.size; + }; + + return subject; +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.worker-expected.txt new file mode 100644 index 0000000000000..f3be2e3eb68d6 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.worker-expected.txt @@ -0,0 +1,18 @@ + +FAIL switchMap(): result subscribes to one inner observable at a time, unsubscribing from the previous active one when a new one replaces it source.switchMap is not a function. (In 'source.switchMap((value, index) => { + if (value === 1) { + return inner1; + } + if (value === 2) { + return inner2; + } + throw new Error("invalid "); + })', 'source.switchMap' is undefined) +FAIL switchMap(): result does not complete when the source observable completes, if the inner observable is still active source.switchMap is not a function. (In 'source.switchMap(() => inner)', 'source.switchMap' is undefined) +FAIL switchMap(): result emits an error if Mapper callback throws an error source.switchMap is not a function. (In 'source.switchMap(() => { + throw e; + })', 'source.switchMap' is undefined) +FAIL switchMap(): result emits an error if the source observable emits an error source.switchMap is not a function. (In 'source.switchMap(() => inner)', 'source.switchMap' is undefined) +FAIL switchMap(): result emits an error if the inner observable emits an error source.switchMap is not a function. (In 'source.switchMap(() => inner)', 'source.switchMap' is undefined) +FAIL switchMap(): should unsubscribe in the correct order when user aborts the subscription source.switchMap is not a function. (In 'source.switchMap(() => inner)', 'source.switchMap' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any-expected.txt new file mode 100644 index 0000000000000..6fc597a768faa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any-expected.txt @@ -0,0 +1,7 @@ + +FAIL take(): Takes the first N values from the source observable, then completes source.take is not a function. (In 'source.take(2)', 'source.take' is undefined) +FAIL take(): Forwards complete()s that happen before the take count is met, and unsubscribes from source Observable source.take is not a function. (In 'source.take(5)', 'source.take' is undefined) +FAIL take(): Should forward errors from the source observable source.take is not a function. (In 'source.take(100)', 'source.take' is undefined) +FAIL take(): take(0) should not subscribe to the source observable, and should return an observable that immediately completes source.take is not a function. (In 'source.take(0)', 'source.take' is undefined) +FAIL take(): Negative count is treated as maximum value source.take is not a function. (In 'source.take(-1)', 'source.take' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.js new file mode 100644 index 0000000000000..8350d0214ccc9 --- /dev/null +++ b/LayoutTests/imported/w3c/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/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.worker-expected.txt new file mode 100644 index 0000000000000..6fc597a768faa --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.worker-expected.txt @@ -0,0 +1,7 @@ + +FAIL take(): Takes the first N values from the source observable, then completes source.take is not a function. (In 'source.take(2)', 'source.take' is undefined) +FAIL take(): Forwards complete()s that happen before the take count is met, and unsubscribes from source Observable source.take is not a function. (In 'source.take(5)', 'source.take' is undefined) +FAIL take(): Should forward errors from the source observable source.take is not a function. (In 'source.take(100)', 'source.take' is undefined) +FAIL take(): take(0) should not subscribe to the source observable, and should return an observable that immediately completes source.take is not a function. (In 'source.take(0)', 'source.take' is undefined) +FAIL take(): Negative count is treated as maximum value source.take is not a function. (In 'source.take(-1)', 'source.take' is undefined) + diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.worker.html b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.worker.html new file mode 100644 index 0000000000000..2382913528e69 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.worker.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any-expected.txt index 5b130f4ef7d7d..ef50513eff2e9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any-expected.txt @@ -1,8 +1,9 @@ FAIL takeUntil subscribes to source Observable and mirrors it uninterrupted promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(new Observable(() => {}))', 'source.takeUntil' is undefined)" FAIL takeUntil subscribes to notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" -FAIL takeUntil: notifier next() unsubscribes to notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" -FAIL takeUntil: notifier error() unsubscribes to notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" +FAIL takeUntil: notifier next() unsubscribes from notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" +FAIL takeUntil: notifier error() unsubscribes from notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" +FAIL takeUntil: notifier throw Error unsubscribes from notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" FAIL takeUntil: notifier next() unsubscribes from notifier & source observable promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" FAIL takeUntil()'s AbortSignal unsubscribes from notifier & source observable promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" FAIL takeUntil: source never subscribed to when notifier synchronously emits a value promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any.js index 6421777e09bc3..f2e99b8cbec88 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any.js +++ b/LayoutTests/imported/w3c/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,15 +174,16 @@ 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", "source subscribed", - "notifier teardown", "notifier signal abort", + "notifier teardown", + "source signal abort", "source teardown", - "source signal abort" + "complete callback", ]); }, "takeUntil: notifier next() unsubscribes from notifier & source observable"); @@ -205,10 +235,10 @@ promise_test(async t => { assert_array_equals(results, [ "notifier subscribed", "source subscribed", - "notifier teardown", "notifier signal abort", + "notifier teardown", + "source signal abort", "source teardown", - "source signal abort" ]); }, "takeUntil()'s AbortSignal unsubscribes from notifier & source observable"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any.worker-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any.worker-expected.txt index 5b130f4ef7d7d..ef50513eff2e9 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any.worker-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any.worker-expected.txt @@ -1,8 +1,9 @@ FAIL takeUntil subscribes to source Observable and mirrors it uninterrupted promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(new Observable(() => {}))', 'source.takeUntil' is undefined)" FAIL takeUntil subscribes to notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" -FAIL takeUntil: notifier next() unsubscribes to notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" -FAIL takeUntil: notifier error() unsubscribes to notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" +FAIL takeUntil: notifier next() unsubscribes from notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" +FAIL takeUntil: notifier error() unsubscribes from notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" +FAIL takeUntil: notifier throw Error unsubscribes from notifier promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" FAIL takeUntil: notifier next() unsubscribes from notifier & source observable promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" FAIL takeUntil()'s AbortSignal unsubscribes from notifier & source observable promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" FAIL takeUntil: source never subscribed to when notifier synchronously emits a value promise_test: Unhandled rejection with value: object "TypeError: source.takeUntil is not a function. (In 'source.takeUntil(notifier)', 'source.takeUntil' is undefined)" diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-toArray.any.js b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-toArray.any.js index 9e6e3abee561d..582bc67453ecb 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-toArray.any.js +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-toArray.any.js @@ -152,9 +152,9 @@ promise_test(async () => { assert_array_equals(results, [ "Subscribed. active: true", - "Outer signal abort event", - "Teardown", "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 @@ -163,12 +163,12 @@ promise_test(async () => { await Promise.resolve(); assert_array_equals(results, [ "Subscribed. active: true", - "Outer signal abort event", - "Teardown", "Inner signal abort event", - "Outer signal Promise", - "Teardown Promise", + "Teardown", + "Outer signal abort event", "Inner signal Promise", + "Teardown Promise", + "Outer signal Promise", ], "Promises resolve in the right order"); }, "Operator Promise abort ordering"); diff --git a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/w3c-import.log index 4a91ffa42584c..2f8f3a4636b3b 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/w3c-import.log +++ b/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/w3c-import.log @@ -17,8 +17,24 @@ List of files: /LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/idlharness.html /LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.any.js /LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-constructor.window.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-drop.any.js /LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-event-target.any.js /LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-event-target.window.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-every.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-filter.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-find.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-first.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-flatMap.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-forEach.window.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-inspect.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-last.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-map.window.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-some.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-switchMap.any.js +/LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-take.any.js /LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.any.js /LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-takeUntil.window.js /LayoutTests/imported/w3c/web-platform-tests/dom/observable/tentative/observable-toArray.any.js diff --git a/LayoutTests/platform/gtk/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt b/LayoutTests/platform/gtk/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt new file mode 100644 index 0000000000000..3e5881f7dec9e --- /dev/null +++ b/LayoutTests/platform/gtk/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt @@ -0,0 +1,15 @@ + +FAIL from(): Observable.from() is a function assert_equals: Observable.from() is a function expected "function" but got "undefined" +PASS from(): Failed conversions +FAIL from(): Given an observable, it returns that exact observable target.on is not a function. (In 'target.on('custom')', 'target.on' is undefined) +FAIL from(): Given an array Observable.from is not a function. (In 'Observable.from(array)', 'Observable.from' is undefined) +FAIL from(): Iterable converts to Observable Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (one observable) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (many observables) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] next() throws error Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): Converts Promise to Observable promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Converts rejected Promise to Observable. No `unhandledrejection` event when error is handled by subscription promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Rejections not handled by subscription are reported to the global, and still not sent as an unhandledrejection event assert_not_equals: Error was reported to the global got disallowed value null +FAIL from(): Observable that implements @@iterator protocol gets converted as an Observable, not iterator Observable.from is not a function. (In 'Observable.from(observable)', 'Observable.from' is undefined) +FAIL from(): Promise that implements @@iterator protocol gets converted as an iterable, not Promise Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined) + diff --git a/LayoutTests/platform/ios/imported/w3c/web-platform-tests/dom/observable/observable-from.any-expected.txt b/LayoutTests/platform/ios/imported/w3c/web-platform-tests/dom/observable/observable-from.any-expected.txt new file mode 100644 index 0000000000000..3e5881f7dec9e --- /dev/null +++ b/LayoutTests/platform/ios/imported/w3c/web-platform-tests/dom/observable/observable-from.any-expected.txt @@ -0,0 +1,15 @@ + +FAIL from(): Observable.from() is a function assert_equals: Observable.from() is a function expected "function" but got "undefined" +PASS from(): Failed conversions +FAIL from(): Given an observable, it returns that exact observable target.on is not a function. (In 'target.on('custom')', 'target.on' is undefined) +FAIL from(): Given an array Observable.from is not a function. (In 'Observable.from(array)', 'Observable.from' is undefined) +FAIL from(): Iterable converts to Observable Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (one observable) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (many observables) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] next() throws error Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): Converts Promise to Observable promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Converts rejected Promise to Observable. No `unhandledrejection` event when error is handled by subscription promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Rejections not handled by subscription are reported to the global, and still not sent as an unhandledrejection event assert_not_equals: Error was reported to the global got disallowed value null +FAIL from(): Observable that implements @@iterator protocol gets converted as an Observable, not iterator Observable.from is not a function. (In 'Observable.from(observable)', 'Observable.from' is undefined) +FAIL from(): Promise that implements @@iterator protocol gets converted as an iterable, not Promise Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined) + diff --git a/LayoutTests/platform/mac-wk1/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt b/LayoutTests/platform/mac-wk1/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt new file mode 100644 index 0000000000000..0ee8bac7eb31a --- /dev/null +++ b/LayoutTests/platform/mac-wk1/imported/w3c/web-platform-tests/dom/observable/tentative/observable-from.any-expected.txt @@ -0,0 +1,17 @@ +CONSOLE MESSAGE: Unhandled Promise Rejection: reason +CONSOLE MESSAGE: Unhandled Promise Rejection: custom reason + +FAIL from(): Observable.from() is a function assert_equals: Observable.from() is a function expected "function" but got "undefined" +PASS from(): Failed conversions +FAIL from(): Given an observable, it returns that exact observable target.on is not a function. (In 'target.on('custom')', 'target.on' is undefined) +FAIL from(): Given an array Observable.from is not a function. (In 'Observable.from(array)', 'Observable.from' is undefined) +FAIL from(): Iterable converts to Observable Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (one observable) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (many observables) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] next() throws error Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): Converts Promise to Observable promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Converts rejected Promise to Observable. No `unhandledrejection` event when error is handled by subscription promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Rejections not handled by subscription are reported to the global, and still not sent as an unhandledrejection event assert_not_equals: Error was reported to the global got disallowed value null +FAIL from(): Observable that implements @@iterator protocol gets converted as an Observable, not iterator Observable.from is not a function. (In 'Observable.from(observable)', 'Observable.from' is undefined) +FAIL from(): Promise that implements @@iterator protocol gets converted as an iterable, not Promise Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined) + diff --git a/LayoutTests/platform/mac-wk2/imported/w3c/web-platform-tests/dom/observable/observable-from.any-expected.txt b/LayoutTests/platform/mac-wk2/imported/w3c/web-platform-tests/dom/observable/observable-from.any-expected.txt new file mode 100644 index 0000000000000..3e5881f7dec9e --- /dev/null +++ b/LayoutTests/platform/mac-wk2/imported/w3c/web-platform-tests/dom/observable/observable-from.any-expected.txt @@ -0,0 +1,15 @@ + +FAIL from(): Observable.from() is a function assert_equals: Observable.from() is a function expected "function" but got "undefined" +PASS from(): Failed conversions +FAIL from(): Given an observable, it returns that exact observable target.on is not a function. (In 'target.on('custom')', 'target.on' is undefined) +FAIL from(): Given an array Observable.from is not a function. (In 'Observable.from(array)', 'Observable.from' is undefined) +FAIL from(): Iterable converts to Observable Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (one observable) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] side-effects (many observables) Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): [Symbol.iterator] next() throws error Observable.from is not a function. (In 'Observable.from(iterable)', 'Observable.from' is undefined) +FAIL from(): Converts Promise to Observable promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Converts rejected Promise to Observable. No `unhandledrejection` event when error is handled by subscription promise_test: Unhandled rejection with value: object "TypeError: Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined)" +FAIL from(): Rejections not handled by subscription are reported to the global, and still not sent as an unhandledrejection event assert_not_equals: Error was reported to the global got disallowed value null +FAIL from(): Observable that implements @@iterator protocol gets converted as an Observable, not iterator Observable.from is not a function. (In 'Observable.from(observable)', 'Observable.from' is undefined) +FAIL from(): Promise that implements @@iterator protocol gets converted as an iterable, not Promise Observable.from is not a function. (In 'Observable.from(promise)', 'Observable.from' is undefined) +