blob: 3d992ddf83af115b722fdd47bd9b01e4ec78804b [file] [log] [blame]
<!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(new TypeError(), function() { new TransformStream(); }, 'TransformStream cannot be constructed with no arguments');
assert_throws(new 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>