| <!DOCTYPE html> | |
| <script src='../../resources/testharness.js'></script> | |
| <script src='../../resources/testharnessreport.js'></script> | |
| <script src='resources/streams-utils.js'></script> | |
| <script> | |
| // This is updated till https://github.com/whatwg/streams/commit/4ba861e6f60c248060811830e11271c84b439cc3 | |
| test(function() { | |
| new TransformStream({ transform: function() { } }); // TransformStream constructed with no errors. | |
| }, 'TransformStream can be constructed with a transform function'); | |
| test(function() { | |
| assert_throws_js(TypeError, function() { new TransformStream(); }, 'TransformStream cannot be constructed with no arguments'); | |
| assert_throws_js(TypeError, function() { new TransformStream({}); }, 'TransformStream cannot be constructed with an empty object'); | |
| }, 'TransformStream cannot be constructed with no transform function'); | |
| test(function() { | |
| var ts = new TransformStream({ transform: function() { } }); | |
| assert_true(Object.prototype.hasOwnProperty.call(ts, 'writable'), 'it has a writable property'); | |
| assert_true(ts.writable instanceof WritableStream, 'writable is an instance of WritableStream'); | |
| assert_true(Object.prototype.hasOwnProperty.call(ts, 'readable'), 'it has a readable property'); | |
| assert_true(ts.readable instanceof ReadableStream, 'readable is an instance of ReadableStream'); | |
| }, 'TransformStream instances must have writable and readable properties of the correct types'); | |
| test(function() { | |
| var ts = new TransformStream({ transform: function() { } }); | |
| assert_equals(ts.writable.state, 'writable', 'writable starts writable'); | |
| }, 'TransformStream writable starts in the writable state'); | |
| var test1 = async_test('Pass-through sync TransformStream: can read from readable what is put into writable'); | |
| test1.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| enqueue(chunk); | |
| done(); | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| assert_equals(ts.writable.state, 'waiting', 'writable is waiting after one write'); | |
| ts.readable.getReader().read().then(test1.step_func(function(result) { | |
| assert_object_equals(result, { value: 'a', done: false }, 'result from reading the readable is the same as was written to writable'); | |
| return ts.writable.ready.then(test1.step_func(function() { | |
| assert_equals(ts.writable.state, 'writable', 'writable becomes writable again'); | |
| test1.done(); | |
| })); | |
| })).catch(test1.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test2 = async_test('Uppercaser sync TransformStream: can read from readable transformed version of what is put into writable'); | |
| test2.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| enqueue(chunk.toUpperCase()); | |
| done(); | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| assert_equals(ts.writable.state, 'waiting', 'writable is waiting after one write'); | |
| ts.readable.getReader().read().then(test2.step_func(function(result) { | |
| assert_object_equals(result, { value: 'A', done: false }, | |
| 'result from reading the readable is the transformation of what was written to writable'); | |
| return ts.writable.ready.then(test2.step_func(function() { | |
| assert_equals(ts.writable.state, 'writable', 'writable becomes writable again'); | |
| test2.done(); | |
| })); | |
| })).catch(test2.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test3 = async_test('Uppercaser-doubler sync TransformStream: can read both chunks put into the readable'); | |
| test3.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| enqueue(chunk.toUpperCase()); | |
| enqueue(chunk.toUpperCase()); | |
| done(); | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| assert_equals(ts.writable.state, 'waiting', 'writable is waiting after one write'); | |
| var reader = ts.readable.getReader(); | |
| reader.read().then(test3.step_func(function(result1) { | |
| assert_object_equals(result1, { value: 'A', done: false }, | |
| 'the first chunk read is the transformation of the single chunk written'); | |
| return reader.read().then(test3.step_func(function(result2) { | |
| assert_object_equals(result2, { value: 'A', done: false }, | |
| 'the second chunk read is also the transformation of the single chunk written'); | |
| return ts.writable.ready.then(test3.step_func(function() { | |
| assert_equals(ts.writable.state, 'writable', 'writable becomes writable again'); | |
| test3.done(); | |
| })); | |
| })); | |
| })).catch(test3.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test4 = async_test('Uppercaser async TransformStream: can read from readable transformed version of what is put into writable'); | |
| test4.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| setTimeout(test4.step_func(function() { enqueue(chunk.toUpperCase()); }), 100); | |
| setTimeout(test4.step_func(done), 500); | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| assert_equals(ts.writable.state, 'waiting', 'writable is waiting after one write'); | |
| ts.readable.getReader().read().then(test4.step_func(function(result) { | |
| assert_object_equals(result, { value: 'A', done: false }, | |
| 'result from reading the readable is the transformation of what was written to writable'); | |
| return ts.writable.ready.then(test4.step_func(function() { | |
| assert_equals(ts.writable.state, 'writable', 'writable becomes writable again'); | |
| test4.done(); | |
| })); | |
| })).catch(test4.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test5 = async_test('Uppercaser-doubler async TransformStream: can read both chunks put into the readable'); | |
| test5.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| setTimeout(test5.step_func(function() { enqueue(chunk.toUpperCase()); }), 100); | |
| setTimeout(test5.step_func(function() { enqueue(chunk.toUpperCase()); }), 500); | |
| setTimeout(test5.step_func(done), 900); | |
| } | |
| }); | |
| var reader = ts.readable.getReader(); | |
| ts.writable.write('a'); | |
| assert_equals(ts.writable.state, 'waiting', 'writable is waiting after one write'); | |
| reader.read().then(test5.step_func(function(result1) { | |
| assert_object_equals(result1, { value: 'A', done: false }, | |
| 'the first chunk read is the transformation of the single chunk written'); | |
| return reader.read().then(test5.step_func(function(result2) { | |
| assert_object_equals(result2, { value: 'A', done: false }, | |
| 'the second chunk read is also the transformation of the single chunk written'); | |
| return ts.writable.ready.then(test5.step_func(function() { | |
| assert_equals(ts.writable.state, 'writable', 'writable becomes writable again'); | |
| test5.done(); | |
| })); | |
| })); | |
| })).catch(test5.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test6 = async_test('TransformStream: by default, closing the writable closes the readable (when there are no queued writes)'); | |
| test6.step(function() | |
| { | |
| var ts = new TransformStream({ transform: function() { } }); | |
| ts.writable.close(); | |
| assert_equals(ts.writable.state, 'closing', 'writable is closing'); | |
| Promise.all([ts.writable.closed, ts.readable.getReader().closed]).then(test6.step_func(function() { | |
| assert_equals(ts.writable.state, 'closed', 'writable state becomes closed eventually'); | |
| test6.done('both writable and readable closed promises fulfill'); | |
| })).catch(test6.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test7 = async_test('TransformStream: by default, closing the writable waits for transforms to finish before closing both'); | |
| test7.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| setTimeout(test7.step_func(done), 500); | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| ts.writable.close(); | |
| assert_equals(ts.writable.state, 'closing', 'writable is closing'); | |
| var rsClosed = false; | |
| ts.readable.getReader().closed.then(test7.step_func(function() { | |
| rsClosed = true; | |
| })); | |
| setTimeout(test7.step_func(function() { | |
| assert_equals(rsClosed, false, 'readable is not closed after a tick'); | |
| ts.writable.closed.then(test7.step_func(function() { | |
| assert_equals(ts.writable.state, 'closed', 'writable becomes closed eventually'); | |
| assert_true(rsClosed, 'readable is closed at that point'); | |
| test7.done(); | |
| })).catch(test7.step_func(function(e) { assert_unreached(e); })); | |
| }), 0); | |
| }); | |
| var test8 = async_test('TransformStream: by default, closing the writable closes the readable after sync enqueues and async done'); | |
| test8.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| enqueue('x'); | |
| enqueue('y'); | |
| setTimeout(test8.step_func(done), 500); | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| ts.writable.close(); | |
| assert_equals(ts.writable.state, 'closing', 'writable is closing'); | |
| ts.writable.closed.then(test8.step_func(function() { | |
| assert_equals(ts.writable.state, 'closed', 'writable becomes closed eventually'); | |
| return readableStreamToArray(ts.readable).then(test8.step_func(function(chunks) { | |
| assert_array_equals(chunks, ['x', 'y'], 'both enqueued chunks can be read from the readable'); | |
| test8.done(); | |
| })); | |
| })).catch(test8.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test9 = async_test('TransformStream: by default, closing the writable closes the readable after async enqueues and async done'); | |
| test9.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| setTimeout(test9.step_func(function() { enqueue('x'); }), 100); | |
| setTimeout(test9.step_func(function() { enqueue('y'); }), 500); | |
| setTimeout(test9.step_func(done), 900); | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| ts.writable.close(); | |
| assert_equals(ts.writable.state, 'closing', 'writable is closing'); | |
| ts.writable.closed.then(test9.step_func(function() { | |
| assert_equals(ts.writable.state, 'closed', 'writable becomes closed eventually'); | |
| return readableStreamToArray(ts.readable).then(test9.step_func(function(chunks) { | |
| assert_array_equals(chunks, ['x', 'y'], 'both enqueued chunks can be read from the readable'); | |
| test9.done(); | |
| })); | |
| })).catch(test9.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test10 = async_test('TransformStream flush is called immediately when the writable is closed, if no writes are queued'); | |
| test10.step(function() | |
| { | |
| var flushCalled = false; | |
| var ts = new TransformStream({ | |
| transform: function() { }, | |
| flush: function(enqueue) { | |
| flushCalled = true; | |
| } | |
| }); | |
| ts.writable.close().then(test10.step_func(function() { | |
| assert_true(flushCalled, 'closing the writable triggers the transform flush immediately'); | |
| test10.done(); | |
| })); | |
| }); | |
| var test11 = async_test('TransformStream flush is called after all queued writes finish, once the writable is closed'); | |
| test11.step(function() | |
| { | |
| var flushCalled = false; | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| setTimeout(test11.step_func(done), 100); | |
| }, | |
| flush: function(enqueue) { | |
| flushCalled = true; | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| ts.writable.close(); | |
| assert_false(flushCalled, 'closing the writable does not immediately call flush if writes are not finished'); | |
| var rsClosed = false; | |
| ts.readable.getReader().closed.then(test11.step_func(function() { | |
| rsClosed = true; | |
| })); | |
| setTimeout(test11.step_func(function() { | |
| assert_true(flushCalled, 'flush is eventually called'); | |
| assert_false(rsClosed, 'if flush does not call close, the readable does not become closed'); | |
| test11.done(); | |
| }), 500); | |
| }); | |
| var test12 = async_test('TransformStream flush gets a chance to enqueue more into the readable'); | |
| test12.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| done(); | |
| }, | |
| flush: function(enqueue) { | |
| enqueue('x'); | |
| enqueue('y'); | |
| } | |
| }); | |
| var reader = ts.readable.getReader(); | |
| ts.writable.write('a'); | |
| ts.writable.close(); | |
| reader.read().then(test12.step_func(function(result1) { | |
| assert_object_equals(result1, { value: 'x', done: false }, 'the first chunk read is the first one enqueued in flush'); | |
| return reader.read().then(test12.step_func(function(result2) { | |
| assert_object_equals(result2, { value: 'y', done: false }, 'the second chunk read is the second one enqueued in flush'); | |
| test12.done(); | |
| })); | |
| })).catch(test12.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test13 = async_test('TransformStream flush gets a chance to enqueue more into the readable, and can then async close'); | |
| test13.step(function() | |
| { | |
| var promiseCalls = 0; | |
| var ts = new TransformStream({ | |
| transform: function(chunk, enqueue, done) { | |
| done(); | |
| }, | |
| flush: function(enqueue, close) { | |
| enqueue('x'); | |
| enqueue('y'); | |
| setTimeout(test13.step_func(close), 100); | |
| } | |
| }); | |
| var reader = ts.readable.getReader(); | |
| ts.writable.write('a'); | |
| ts.writable.close(); | |
| reader.read().then(test13.step_func(function(result1) { | |
| assert_equals(++promiseCalls, 1); | |
| assert_object_equals(result1, { value: 'x', done: false }, 'the first chunk read is the first one enqueued in flush'); | |
| return reader.read().then(test13.step_func(function(result2) { | |
| assert_equals(++promiseCalls, 2); | |
| assert_object_equals(result2, { value: 'y', done: false }, 'the second chunk read is the second one enqueued in flush'); | |
| })); | |
| })).catch(test13.step_func(function(e) { assert_unreached(e); })); | |
| reader.closed.then(test13.step_func(function() { | |
| assert_equals(++promiseCalls, 3); | |
| test13.done('readable reader becomes closed'); | |
| })).catch(test13.step_func(function(e) { assert_unreached(e); })); | |
| }); | |
| var test14 = async_test('Transform stream should call transformer methods as methods'); | |
| test14.step(function() | |
| { | |
| var ts = new TransformStream({ | |
| suffix: '-suffix', | |
| transform: function(chunk, enqueue, done) { | |
| enqueue(chunk + this.suffix); | |
| done(); | |
| }, | |
| flush: function(enqueue, close) { | |
| enqueue('flushed' + this.suffix); | |
| close(); | |
| } | |
| }); | |
| ts.writable.write('a'); | |
| ts.writable.close(); | |
| ts.writable.closed.then( | |
| test14.step_func(function() { | |
| return readableStreamToArray(ts.readable).then(test14.step_func(function(chunks) { | |
| assert_array_equals(chunks, ['a-suffix', 'flushed-suffix'], 'both enqueued chunks have suffixes'); | |
| test14.done(); | |
| })); | |
| }), | |
| test14.step_func(function(e) { assert_unreached(e); }) | |
| ); | |
| }); | |
| </script> |