From 1e0735743e4ab812c54f74bc10f1312a2c42e7a6 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Tue, 2 Apr 2024 20:45:45 -0700 Subject: [PATCH] DOM: Implement the `first()` Observable operator See https://github.com/WICG/observable/pull/131. For WPTs: Co-authored-by: ben@benlesh.com R=jarhar@chromium.org Bug: 40282760 Change-Id: I81a2b482dcfdbc149d60ddbc5b98ddadb6dafc89 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5412443 Reviewed-by: Joey Arhar Commit-Queue: Dominic Farolino Cr-Commit-Position: refs/heads/main@{#1281639} --- .../tentative/observable-first.any.js | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 dom/observable/tentative/observable-first.any.js diff --git a/dom/observable/tentative/observable-first.any.js b/dom/observable/tentative/observable-first.any.js new file mode 100644 index 00000000000000..7c99066dc22a82 --- /dev/null +++ b/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 teardown", + "source abort", + "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 teardown", + "source abort", + "after source next 1", + "first resolved with: 1", + ], "Array values after Promise is awaited"); +}, "first(): Lifecycle");