| 'use strict'; |
| |
| if (self.importScripts) { |
| self.importScripts('/resources/testharness.js'); |
| self.importScripts('../resources/rs-utils.js'); |
| self.importScripts('../resources/test-utils.js'); |
| } |
| |
| function duckTypedPassThroughTransform() { |
| let enqueueInReadable; |
| let closeReadable; |
| |
| return { |
| writable: new WritableStream({ |
| write(chunk) { |
| enqueueInReadable(chunk); |
| }, |
| |
| close() { |
| closeReadable(); |
| } |
| }), |
| |
| readable: new ReadableStream({ |
| start(c) { |
| enqueueInReadable = c.enqueue.bind(c); |
| closeReadable = c.close.bind(c); |
| } |
| }) |
| }; |
| } |
| |
| function uninterestingReadableWritablePair() { |
| return { writable: new WritableStream(), readable: new ReadableStream() }; |
| } |
| |
| promise_test(() => { |
| const readableEnd = sequentialReadableStream(5).pipeThrough(duckTypedPassThroughTransform()); |
| |
| return readableStreamToArray(readableEnd).then(chunks => |
| assert_array_equals(chunks, [1, 2, 3, 4, 5]), 'chunks should match'); |
| }, 'Piping through a duck-typed pass-through transform stream should work'); |
| |
| promise_test(() => { |
| const transform = { |
| writable: new WritableStream({ |
| start(c) { |
| c.error(new Error('this rejection should not be reported as unhandled')); |
| } |
| }), |
| readable: new ReadableStream() |
| }; |
| |
| sequentialReadableStream(5).pipeThrough(transform); |
| |
| // The test harness should complain about unhandled rejections by then. |
| return flushAsyncEvents(); |
| |
| }, 'Piping through a transform errored on the writable end does not cause an unhandled promise rejection'); |
| |
| test(() => { |
| let calledWithArgs; |
| const dummy = { |
| pipeTo(...args) { |
| calledWithArgs = args; |
| |
| // Does not return anything, testing the spec's guard against trying to mark [[PromiseIsHandled]] on undefined. |
| } |
| }; |
| |
| const fakeWritable = { fake: 'writable' }; |
| const fakeReadable = { fake: 'readable' }; |
| const arg2 = { arg: 'arg2' }; |
| const arg3 = { arg: 'arg3' }; |
| const result = |
| ReadableStream.prototype.pipeThrough.call(dummy, { writable: fakeWritable, readable: fakeReadable }, arg2, arg3); |
| |
| assert_array_equals(calledWithArgs, [fakeWritable, arg2], |
| 'The this value\'s pipeTo method should be called with the appropriate arguments'); |
| assert_equals(result, fakeReadable, 'return value should be the passed readable property'); |
| |
| }, 'pipeThrough generically calls pipeTo with the appropriate args'); |
| |
| test(() => { |
| const dummy = { |
| pipeTo() { |
| return { not: 'a promise' }; |
| } |
| }; |
| |
| ReadableStream.prototype.pipeThrough.call(dummy, uninterestingReadableWritablePair()); |
| |
| // Test passes if this doesn't throw or crash. |
| |
| }, 'pipeThrough can handle calling a pipeTo that returns a non-promise object'); |
| |
| test(() => { |
| const dummy = { |
| pipeTo() { |
| return { |
| then() {}, |
| this: 'is not a real promise' |
| }; |
| } |
| }; |
| |
| ReadableStream.prototype.pipeThrough.call(dummy, uninterestingReadableWritablePair()); |
| |
| // Test passes if this doesn't throw or crash. |
| |
| }, 'pipeThrough can handle calling a pipeTo that returns a non-promise thenable object'); |
| |
| promise_test(() => { |
| const dummy = { |
| pipeTo() { |
| return Promise.reject(new Error('this rejection should not be reported as unhandled')); |
| } |
| }; |
| |
| ReadableStream.prototype.pipeThrough.call(dummy, uninterestingReadableWritablePair()); |
| |
| // The test harness should complain about unhandled rejections by then. |
| return flushAsyncEvents(); |
| |
| }, 'pipeThrough should mark a real promise from a fake readable as handled'); |
| |
| test(() => { |
| let thenCalled = false; |
| let catchCalled = false; |
| const dummy = { |
| pipeTo() { |
| const fakePromise = Object.create(Promise.prototype); |
| fakePromise.then = () => { |
| thenCalled = true; |
| }; |
| fakePromise.catch = () => { |
| catchCalled = true; |
| }; |
| assert_true(fakePromise instanceof Promise, 'fakePromise fools instanceof'); |
| return fakePromise; |
| } |
| }; |
| |
| // An incorrect implementation which uses an internal method to mark the promise as handled will throw or crash here. |
| ReadableStream.prototype.pipeThrough.call(dummy, uninterestingReadableWritablePair()); |
| |
| // An incorrect implementation that tries to mark the promise as handled by calling .then() or .catch() on the object |
| // will fail these tests. |
| assert_false(thenCalled, 'then should not be called'); |
| assert_false(catchCalled, 'catch should not be called'); |
| }, 'pipeThrough should not be fooled by an object whose instanceof Promise returns true'); |
| |
| test(() => { |
| const pairs = [ |
| {}, |
| { readable: undefined, writable: undefined }, |
| { readable: 'readable' }, |
| { readable: 'readable', writable: undefined }, |
| { writable: 'writable' }, |
| { readable: undefined, writable: 'writable' } |
| ]; |
| for (let i = 0; i < pairs.length; ++i) { |
| const pair = pairs[i]; |
| const rs = new ReadableStream(); |
| assert_throws(new TypeError(), () => rs.pipeThrough(pair), |
| `pipeThrough should throw for argument ${JSON.stringify(pair)} (index ${i});`); |
| } |
| }, 'undefined readable or writable arguments should cause pipeThrough to throw'); |
| |
| test(() => { |
| const invalidArguments = [null, 0, NaN, '', [], {}, false, () => {}]; |
| for (const arg of invalidArguments) { |
| const rs = new ReadableStream(); |
| assert_equals(arg, rs.pipeThrough({ writable: new WritableStream(), readable: arg }), |
| 'pipeThrough() should not throw for readable: ' + JSON.stringify(arg)); |
| const rs2 = new ReadableStream(); |
| assert_equals(rs2, rs.pipeThrough({ writable: arg, readable: rs2 }), |
| 'pipeThrough() should not throw for writable: ' + JSON.stringify(arg)); |
| } |
| }, 'invalid but not undefined arguments should not cause pipeThrough to throw'); |
| |
| test(() => { |
| |
| const thisValue = { |
| pipeTo() { |
| assert_unreached('pipeTo should not be called'); |
| } |
| }; |
| |
| methodThrows(ReadableStream.prototype, 'pipeThrough', thisValue, [undefined, {}]); |
| methodThrows(ReadableStream.prototype, 'pipeThrough', thisValue, [null, {}]); |
| |
| }, 'pipeThrough should throw when its first argument is not convertible to an object'); |
| |
| test(() => { |
| |
| const args = [{ readable: {}, writable: {} }, {}]; |
| |
| methodThrows(ReadableStream.prototype, 'pipeThrough', undefined, args); |
| methodThrows(ReadableStream.prototype, 'pipeThrough', null, args); |
| methodThrows(ReadableStream.prototype, 'pipeThrough', 1, args); |
| methodThrows(ReadableStream.prototype, 'pipeThrough', { pipeTo: 'test' }, args); |
| |
| }, 'pipeThrough should throw when "this" has no pipeTo method'); |
| |
| test(() => { |
| const error = new Error('potato'); |
| |
| const throwingPipeTo = { |
| get pipeTo() { |
| throw error; |
| } |
| }; |
| assert_throws(error, |
| () => ReadableStream.prototype.pipeThrough.call(throwingPipeTo, { readable: { }, writable: { } }, {}), |
| 'pipeThrough should rethrow the error thrown by pipeTo'); |
| |
| const thisValue = { |
| pipeTo() { |
| assert_unreached('pipeTo should not be called'); |
| } |
| }; |
| |
| const throwingWritable = { |
| readable: {}, |
| get writable() { |
| throw error; |
| } |
| }; |
| assert_throws(error, |
| () => ReadableStream.prototype.pipeThrough.call(thisValue, throwingWritable, {}), |
| 'pipeThrough should rethrow the error thrown by the writable getter'); |
| |
| const throwingReadable = { |
| get readable() { |
| throw error; |
| }, |
| writable: {} |
| }; |
| assert_throws(error, |
| () => ReadableStream.prototype.pipeThrough.call(thisValue, throwingReadable, {}), |
| 'pipeThrough should rethrow the error thrown by the readable getter'); |
| |
| }, 'pipeThrough should rethrow errors from accessing pipeTo, readable, or writable'); |
| |
| test(() => { |
| |
| let count = 0; |
| const thisValue = { |
| pipeTo() { |
| ++count; |
| } |
| }; |
| |
| ReadableStream.prototype.pipeThrough.call(thisValue, { readable: {}, writable: {} }); |
| |
| assert_equals(count, 1, 'pipeTo was called once'); |
| |
| }, 'pipeThrough should work with no options argument'); |
| |
| done(); |