observable-toArray.any.js (6537B)
1 // Because we test that the global error handler is called at various times. 2 setup({allow_uncaught_exception: true}); 3 4 promise_test(async () => { 5 const observable = new Observable(subscriber => { 6 subscriber.next(1); 7 subscriber.next(2); 8 subscriber.next(3); 9 subscriber.complete(); 10 }); 11 12 const array = await observable.toArray(); 13 assert_array_equals(array, [1, 2, 3]); 14 }, "toArray(): basic next/complete"); 15 16 promise_test(async t => { 17 let errorReported = null; 18 let innerSubscriber = null; 19 self.addEventListener('error', e => errorReported = e, {once: true}); 20 21 const error = new Error("custom error"); 22 const observable = new Observable(subscriber => { 23 innerSubscriber = subscriber; 24 subscriber.error(error); 25 }); 26 27 try { 28 const array = await observable.toArray(); 29 assert_unreached("toArray() promise must not resolve"); 30 } catch (e) { 31 assert_equals(e, error); 32 assert_equals(errorReported, null); 33 34 // Calls to `error()` after the subscription is closed still report the 35 // exception. 36 innerSubscriber.error(error); 37 assert_not_equals(errorReported, null, "Exception was reported to global"); 38 assert_true(errorReported.message.includes("custom error"), "Error message matches"); 39 assert_greater_than(errorReported.lineno, 0, "Error lineno is greater than 0"); 40 assert_greater_than(errorReported.colno, 0, "Error lineno is greater than 0"); 41 assert_equals(errorReported.error, error, "Error object is equivalent"); 42 } 43 }, "toArray(): first error() rejects promise; subsequent error()s report the exceptions"); 44 45 promise_test(async t => { 46 let errorReported = null; 47 let innerSubscriber = null; 48 self.addEventListener('error', e => errorReported = e, {once: true}); 49 50 const error = new Error("custom error"); 51 const observable = new Observable(subscriber => { 52 innerSubscriber = subscriber; 53 subscriber.complete(); 54 }); 55 56 const array = await observable.toArray(); 57 assert_array_equals(array, []); 58 assert_equals(errorReported, null); 59 60 // Calls to `error()` after the subscription is closed still report the 61 // exception. 62 innerSubscriber.error(error); 63 assert_not_equals(errorReported, null, "Exception was reported to global"); 64 assert_true(errorReported.message.includes("custom error"), "Error message matches"); 65 assert_greater_than(errorReported.lineno, 0, "Error lineno is greater than 0"); 66 assert_greater_than(errorReported.colno, 0, "Error lineno is greater than 0"); 67 assert_equals(errorReported.error, error, "Error object is equivalent"); 68 }, "toArray(): complete() resolves promise; subsequent error()s report the exceptions"); 69 70 promise_test(async () => { 71 // This tracks whether `postSubscriptionPromise` has had its then handler run. 72 // This helps us keep track of the timing/ordering of everything. Calling a 73 // Promise-returning operator with an aborted signal must *immediately* reject 74 // the returned Promise, which means code "awaiting" it should run before any 75 // subsequent Promise resolution/rejection handlers are run. 76 let postSubscriptionPromiseResolved = false; 77 let subscriptionImmediatelyInactive = false; 78 79 const observable = new Observable(subscriber => { 80 const inactive = !subscriber.active; 81 subscriptionImmediatelyInactive = inactive; 82 }); 83 84 const rejectedPromise = observable.toArray({signal: AbortSignal.abort()}) 85 .then(() => { 86 assert_unreached("Operator promise must not resolve its abort signal is " + 87 "rejected"); 88 }, () => { 89 // See the documentation above. The rejection handler (i.e., this code) for 90 // immediately-aborted operator Promises runs before any later-scheduled 91 // Promise resolution/rejections. 92 assert_false(postSubscriptionPromiseResolved, 93 "Operator promise rejects before later promise"); 94 }); 95 const postSubscriptionPromise = 96 Promise.resolve().then(() => postSubscriptionPromiseResolved = true); 97 98 await rejectedPromise; 99 }, "toArray(): Subscribing with an aborted signal returns an immediately " + 100 "rejected promise"); 101 102 promise_test(async () => { 103 let postSubscriptionPromiseResolved = false; 104 105 const observable = new Observable(subscriber => {}); 106 const controller = new AbortController(); 107 const arrayPromise = observable.toArray({signal: controller.signal}) 108 .then(() => { 109 assert_unreached("Operator promise must not resolve if its abort signal " + 110 "is rejected"); 111 }, () => { 112 assert_false(postSubscriptionPromiseResolved, 113 "controller.abort() synchronously rejects the operator " + 114 "Promise"); 115 }); 116 117 // This must synchronously reject `arrayPromise`, scheduling in the next 118 // microtask. 119 controller.abort(); 120 Promise.resolve().then(value => postSubscriptionPromiseResolved = true); 121 122 await arrayPromise; 123 }, "toArray(): Aborting the passed-in signal rejects the returned promise"); 124 125 // See https://github.com/WICG/observable/issues/96 for discussion about this. 126 promise_test(async () => { 127 const results = []; 128 129 const observable = new Observable(subscriber => { 130 results.push(`Subscribed. active: ${subscriber.active}`); 131 132 subscriber.signal.addEventListener('abort', e => { 133 results.push("Inner signal abort event"); 134 Promise.resolve("Inner signal Promise").then(value => results.push(value)); 135 }); 136 137 subscriber.addTeardown(() => { 138 results.push("Teardown"); 139 Promise.resolve("Teardown Promise").then(value => results.push(value)); 140 }); 141 }); 142 143 const controller = new AbortController(); 144 controller.signal.addEventListener('abort', e => { 145 results.push("Outer signal abort event"); 146 Promise.resolve("Outer signal Promise").then(value => results.push(value)); 147 }); 148 149 // Subscribe. 150 observable.toArray({signal: controller.signal}); 151 controller.abort(); 152 153 assert_array_equals(results, [ 154 "Subscribed. active: true", 155 "Inner signal abort event", 156 "Teardown", 157 "Outer signal abort event", 158 ], "Events and teardowns are fired in the right ordered"); 159 160 // Everything microtask above should be queued up by now, so queue one more 161 // final microtask that will run after all of the others, wait for it, and the 162 // check `results` is right. 163 await Promise.resolve(); 164 assert_array_equals(results, [ 165 "Subscribed. active: true", 166 "Inner signal abort event", 167 "Teardown", 168 "Outer signal abort event", 169 "Inner signal Promise", 170 "Teardown Promise", 171 "Outer signal Promise", 172 ], "Promises resolve in the right order"); 173 }, "Operator Promise abort ordering");