| // META: global=worker,jsshell |
| // META: script=../resources/test-utils.js |
| // META: script=../resources/recording-streams.js |
| 'use strict'; |
| |
| const error1 = new Error('error1'); |
| error1.name = 'error1'; |
| |
| const error2 = new Error('error2'); |
| error2.name = 'error2'; |
| |
| function writeArrayToStream(array, writableStreamWriter) { |
| array.forEach(chunk => writableStreamWriter.write(chunk)); |
| return writableStreamWriter.close(); |
| } |
| |
| promise_test(() => { |
| let storage; |
| const ws = new WritableStream({ |
| start() { |
| storage = []; |
| }, |
| |
| write(chunk) { |
| return delay(0).then(() => storage.push(chunk)); |
| }, |
| |
| close() { |
| return delay(0); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| const input = [1, 2, 3, 4, 5]; |
| return writeArrayToStream(input, writer) |
| .then(() => assert_array_equals(storage, input, 'correct data should be relayed to underlying sink')); |
| }, 'WritableStream should complete asynchronous writes before close resolves'); |
| |
| promise_test(() => { |
| const ws = recordingWritableStream(); |
| |
| const writer = ws.getWriter(); |
| |
| const input = [1, 2, 3, 4, 5]; |
| return writeArrayToStream(input, writer) |
| .then(() => assert_array_equals(ws.events, ['write', 1, 'write', 2, 'write', 3, 'write', 4, 'write', 5, 'close'], |
| 'correct data should be relayed to underlying sink')); |
| }, 'WritableStream should complete synchronous writes before close resolves'); |
| |
| promise_test(() => { |
| const ws = new WritableStream({ |
| write() { |
| return 'Hello'; |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| const writePromise = writer.write('a'); |
| return writePromise |
| .then(value => assert_equals(value, undefined, 'fulfillment value must be undefined')); |
| }, 'fulfillment value of ws.write() call should be undefined even if the underlying sink returns a non-undefined ' + |
| 'value'); |
| |
| promise_test(() => { |
| let resolveSinkWritePromise; |
| const ws = new WritableStream({ |
| write() { |
| return new Promise(resolve => { |
| resolveSinkWritePromise = resolve; |
| }); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| assert_equals(writer.desiredSize, 1, 'desiredSize should be 1'); |
| |
| return writer.ready.then(() => { |
| const writePromise = writer.write('a'); |
| let writePromiseResolved = false; |
| assert_not_equals(resolveSinkWritePromise, undefined, 'resolveSinkWritePromise should not be undefined'); |
| |
| assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after writer.write()'); |
| |
| return Promise.all([ |
| writePromise.then(value => { |
| writePromiseResolved = true; |
| assert_equals(resolveSinkWritePromise, undefined, 'sinkWritePromise should be fulfilled before writePromise'); |
| |
| assert_equals(value, undefined, 'writePromise should be fulfilled with undefined'); |
| }), |
| writer.ready.then(value => { |
| assert_equals(resolveSinkWritePromise, undefined, 'sinkWritePromise should be fulfilled before writer.ready'); |
| assert_true(writePromiseResolved, 'writePromise should be fulfilled before writer.ready'); |
| |
| assert_equals(writer.desiredSize, 1, 'desiredSize should be 1 again'); |
| |
| assert_equals(value, undefined, 'writePromise should be fulfilled with undefined'); |
| }), |
| flushAsyncEvents().then(() => { |
| resolveSinkWritePromise(); |
| resolveSinkWritePromise = undefined; |
| }) |
| ]); |
| }); |
| }, 'WritableStream should transition to waiting until write is acknowledged'); |
| |
| promise_test(t => { |
| let sinkWritePromiseRejectors = []; |
| const ws = new WritableStream({ |
| write() { |
| const sinkWritePromise = new Promise((r, reject) => sinkWritePromiseRejectors.push(reject)); |
| return sinkWritePromise; |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| assert_equals(writer.desiredSize, 1, 'desiredSize should be 1'); |
| |
| return writer.ready.then(() => { |
| const writePromise = writer.write('a'); |
| assert_equals(sinkWritePromiseRejectors.length, 1, 'there should be 1 rejector'); |
| assert_equals(writer.desiredSize, 0, 'desiredSize should be 0'); |
| |
| const writePromise2 = writer.write('b'); |
| assert_equals(sinkWritePromiseRejectors.length, 1, 'there should be still 1 rejector'); |
| assert_equals(writer.desiredSize, -1, 'desiredSize should be -1'); |
| |
| const closedPromise = writer.close(); |
| |
| assert_equals(writer.desiredSize, -1, 'desiredSize should still be -1'); |
| |
| return Promise.all([ |
| promise_rejects(t, error1, closedPromise, |
| 'closedPromise should reject with the error returned from the sink\'s write method') |
| .then(() => assert_equals(sinkWritePromiseRejectors.length, 0, |
| 'sinkWritePromise should reject before closedPromise')), |
| promise_rejects(t, error1, writePromise, |
| 'writePromise should reject with the error returned from the sink\'s write method') |
| .then(() => assert_equals(sinkWritePromiseRejectors.length, 0, |
| 'sinkWritePromise should reject before writePromise')), |
| promise_rejects(t, error1, writePromise2, |
| 'writePromise2 should reject with the error returned from the sink\'s write method') |
| .then(() => assert_equals(sinkWritePromiseRejectors.length, 0, |
| 'sinkWritePromise should reject before writePromise2')), |
| flushAsyncEvents().then(() => { |
| sinkWritePromiseRejectors[0](error1); |
| sinkWritePromiseRejectors = []; |
| }) |
| ]); |
| }); |
| }, 'when write returns a rejected promise, queued writes and close should be cleared'); |
| |
| promise_test(t => { |
| const ws = new WritableStream({ |
| write() { |
| throw error1; |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| return promise_rejects(t, error1, writer.write('a'), |
| 'write() should reject with the error returned from the sink\'s write method') |
| .then(() => promise_rejects(t, new TypeError(), writer.close(), 'close() should be rejected')); |
| }, 'when sink\'s write throws an error, the stream should become errored and the promise should reject'); |
| |
| promise_test(t => { |
| const ws = new WritableStream({ |
| write(chunk, controller) { |
| controller.error(error1); |
| throw error2; |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| return promise_rejects(t, error2, writer.write('a'), |
| 'write() should reject with the error returned from the sink\'s write method ') |
| .then(() => { |
| return Promise.all([ |
| promise_rejects(t, error1, writer.ready, |
| 'writer.ready must reject with the error passed to the controller'), |
| promise_rejects(t, error1, writer.closed, |
| 'writer.closed must reject with the error passed to the controller') |
| ]); |
| }); |
| }, 'writer.write(), ready and closed reject with the error passed to controller.error() made before sink.write' + |
| ' rejection'); |
| |
| promise_test(() => { |
| const numberOfWrites = 1000; |
| |
| let resolveFirstWritePromise; |
| let writeCount = 0; |
| const ws = new WritableStream({ |
| write() { |
| ++writeCount; |
| if (!resolveFirstWritePromise) { |
| return new Promise(resolve => { |
| resolveFirstWritePromise = resolve; |
| }); |
| } |
| return Promise.resolve(); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| return writer.ready.then(() => { |
| for (let i = 1; i < numberOfWrites; ++i) { |
| writer.write('a'); |
| } |
| const writePromise = writer.write('a'); |
| |
| assert_equals(writeCount, 1, 'should have called sink\'s write once'); |
| |
| resolveFirstWritePromise(); |
| |
| return writePromise |
| .then(() => |
| assert_equals(writeCount, numberOfWrites, `should have called sink's write ${numberOfWrites} times`)); |
| }); |
| }, 'a large queue of writes should be processed completely'); |
| |
| promise_test(() => { |
| const stream = recordingWritableStream(); |
| const w = stream.getWriter(); |
| const WritableStreamDefaultWriter = w.constructor; |
| w.releaseLock(); |
| const writer = new WritableStreamDefaultWriter(stream); |
| return writer.ready.then(() => { |
| writer.write('a'); |
| assert_array_equals(stream.events, ['write', 'a'], 'write() should be passed to sink'); |
| }); |
| }, 'WritableStreamDefaultWriter should work when manually constructed'); |
| |
| promise_test(() => { |
| let thenCalled = false; |
| const ws = new WritableStream({ |
| write() { |
| return { |
| then(onFulfilled) { |
| thenCalled = true; |
| onFulfilled(); |
| } |
| }; |
| } |
| }); |
| return ws.getWriter().write('a').then(() => assert_true(thenCalled, 'thenCalled should be true')); |
| }, 'returning a thenable from write() should work'); |
| |
| promise_test(() => { |
| const stream = new WritableStream(); |
| const writer = stream.getWriter(); |
| const WritableStreamDefaultWriter = writer.constructor; |
| assert_throws(new TypeError(), () => new WritableStreamDefaultWriter(stream), |
| 'should not be able to construct on locked stream'); |
| // If stream.[[writer]] no longer points to |writer| then the closed Promise |
| // won't work properly. |
| return Promise.all([writer.close(), writer.closed]); |
| }, 'failing DefaultWriter constructor should not release an existing writer'); |
| |
| promise_test(t => { |
| const ws = new WritableStream({ |
| start() { |
| return Promise.reject(error1); |
| } |
| }, { highWaterMark: 0 }); |
| const writer = ws.getWriter(); |
| return Promise.all([ |
| promise_rejects(t, error1, writer.ready, 'ready should be rejected'), |
| promise_rejects(t, error1, writer.write(), 'write() should be rejected') |
| ]); |
| }, 'write() on a stream with HWM 0 should not cause the ready Promise to resolve'); |