<!DOCTYPE html>
<script>
// Silence unhandled rejection messages.
window.addEventListener("unhandledrejection", function(event) {
    event.stopImmediatePropagation();
    event.preventDefault();
});
</script>
<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

function templatedRSClosed(label, factory) {
    test(function() {
    }, 'Running templatedRSClosed with ' + label);

    var test1 = async_test(label + ': piping to a WritableStream in the writable state should close the writable stream');
    test1.step(function() {
        var closeCalled = false;

        var rs = factory();

        var startPromise = Promise.resolve();
        var ws = new WritableStream({
            start: function() {
                return startPromise;
            },
            write: function() {
                assert_unreached('Unexpected write call');
            },
            close: function() {
                closeCalled = true;
            },
            abort: function() {
                assert_unreached('Unexpected abort call');
            }
        });

        startPromise.then(test1.step_func(function() {
            assert_equals(ws.state, 'writable', 'writable stream should start in writable state');

            return rs.pipeTo(ws).then(test1.step_func(function() {
                assert_true(closeCalled);
                assert_equals(ws.state, 'closed', 'writable stream should become closed');
                test1.done('underlying source close should be called');
            }));
        })).catch(test1.step_func(function(e) { assert_unreached(e); }));
    });

    var test2 = async_test(label + ': piping to a WritableStream in the writable state with { preventClose: true } should do nothing');
    test2.step(function() {
        var rs = factory();

        var startPromise = Promise.resolve();
        var ws = new WritableStream({
            start: function() {
                return startPromise;
            },
            write: function() {
                assert_unreached('Unexpected write call');
            },
            close: function() {
                assert_unreached('Unexpected close call');
            },
            abort: function() {
                assert_unreached('Unexpected abort call');
            }
        });

        startPromise.then(test2.step_func(function() {
            assert_equals(ws.state, 'writable', 'writable stream should start in writable state');

            return rs.pipeTo(ws, { preventClose: true }).then(test2.step_func(function() {
                assert_equals(ws.state, 'writable', 'writable stream should still be writable');
                test2.done('pipeTo promise should be fulfilled');
            }));
        })).catch(test2.step_func(function(e) { assert_unreached(e); }));
    });
};

function templatedRSErrored(label, factory, error) {
    test(function() {
    }, 'Running templatedRSErrored with ' + label);

    var test1 = async_test(label + ': piping to a WritableStream in the writable state should abort the writable stream');
    test1.step(function() {
        var rs = factory();

        var startPromise = Promise.resolve();
        var ws = new WritableStream({
            start: function() {
                return startPromise;
            },
            write: function() {
                assert_unreached('Unexpected write call');
            },
            close: function() {
                assert_reached('Unexpected close call');
            },
            abort: function(reason) {
                assert_equals(reason, error);
            }
        });

        startPromise.then(test1.step_func(function() {
            assert_equals(ws.state, 'writable');

            rs.pipeTo(ws).then(
                test1.step_func(function() { assert_unreached('pipeTo promise should not be fulfilled'); }),
                test1.step_func(function(e) {
                    assert_equals(e, error, 'pipeTo promise should be rejected with the passed error');
                    assert_equals(ws.state, 'errored', 'writable stream should become errored');
                    test1.done();
                })
            );
        }));
    });
};

function templatedRSErroredAsyncOnly(label, factory, error) {
    test(function() {
    }, 'Running templatedRSErroredAsyncOnly with ' + label);

    var test1 = async_test(label + ': piping with no options');
    test1.step(function() {
        var closeCalled = false;

        var rs = factory();

        var ws = new WritableStream({
            abort: function(r) {
                assert_equals(r, error, 'reason passed to abort should equal the source error');
            }
        });

        rs.pipeTo(ws).catch(test1.step_func(function(e) {
            assert_equals(ws.state, 'errored', 'destination should be errored');
            assert_equals(e, error, 'rejection reason of pipeToPromise should be the source error');
            assert_true(closeCalled);
            test1.done();
        }));

        ws.closed.catch(test1.step_func(function(e) {
            assert_equals(e, error, 'rejection reason of dest closed should be the source error');
            closeCalled = true;
        }))
    });

    var test2 = async_test(label + ': piping with { preventAbort: false }');
    test2.step(function() {
        var abortCalled = false;
        var closeRejected = false;

        var rs = factory();

        var ws = new WritableStream({
            abort: function(r) {
                assert_equals(r, error, 'reason passed to abort should equal the source error');
                abortCalled = true;
            }
        });

        rs.pipeTo(ws, { preventAbort: false }).catch(test2.step_func(function(e) {
            assert_equals(ws.state, 'errored', 'destination should be errored');
            assert_equals(e, error, 'rejection reason of pipeToPromise should be the source error');
            assert_true(abortCalled);
            assert_true(closeRejected);
            test2.done();
        }));

        ws.closed.catch(test2.step_func(function(e) {
            assert_equals(e, error, 'rejection reason of dest closed should be the source error');
            closeRejected = true;
        }));
    });

    var test3 = async_test(label + ': piping with { preventAbort: true }');
    test3.step(function() {
        var rs = factory();

        var ws = new WritableStream({
            abort: function() {
                assert_unreached('underlying sink abort should not be called');
            }
        });

        rs.pipeTo(ws, { preventAbort: true }).catch(test3.step_func(function(e) {
            assert_equals(ws.state, 'writable', 'destination should remain writable');
            assert_equals(e, error, 'rejection reason of pipeToPromise should be the source error');
            test3.done();
        }));
   });
};

function templatedRSTwoChunksClosed(label, factory, error) {
    test(function() {
    }, 'Running templatedRSTwoChunksClosed with ' + label);

    var test1 = async_test(label + ': piping with no options and no destination errors');
    test1.step(function() {
        var rs = factory();

        var chunksWritten = [];
        var ws = new WritableStream({
            abort: function() {
                assert_unreached('unexpected abort call');
            },
            write: function(chunk) {
                chunksWritten.push(chunk);
            }
        });

        rs.pipeTo(ws).then(test1.step_func(function() {
            assert_equals(ws.state, 'closed', 'destination should be closed');
            assert_array_equals(chunksWritten, chunks);
            test1.done();
        }));
    });

    var test2 = async_test(label + ': piping with { preventClose: false } and no destination errors');
    test2.step(function() {
        var rs = factory();

        var chunksWritten = [];
        var ws = new WritableStream({
            abort: function() {
                assert_unreached('unexpected abort call');
            },
            write: function(chunk) {
                chunksWritten.push(chunk);
            }
        });

        rs.pipeTo(ws).then(test2.step_func(function() {
            assert_equals(ws.state, 'closed', 'destination should be closed');
            assert_array_equals(chunksWritten, chunks);
            test2.done();
        }));
    });

    var test3 = async_test(label + ': piping with { preventClose: true } and no destination errors');
    test3.step(function() {
        var rs = factory();

        var chunksWritten = [];
        var ws = new WritableStream({
            close: function() {
                assert_unreached('unexpected close call');
            },
            abort: function() {
                assert_unreached('unexpected abort call');
            },
            write: function(chunk) {
                chunksWritten.push(chunk);
            }
        });

        rs.pipeTo(ws, { preventClose: true }).then(test3.step_func(function() {
            assert_equals(ws.state, 'writable', 'destination should be writable');
            assert_array_equals(chunksWritten, chunks);
            test3.done();
        }));
    });

    var test4 = async_test(label + ': piping with { preventClose: false } and a destination with that errors synchronously');
    test4.step(function() {
        var rs = factory();

        var theError = new Error('!!!');
        var ws = new WritableStream({
            close: function() {
                assert_unreached('unexpected close call');
            },
            abort: function() {
                assert_unreached('unexpected abort call');
            },
            write: function() {
                throw theError;
            }
        });

        rs.pipeTo(ws, { preventClose: false }).then(
            test4.step_func(function() { assert_unreached('pipeTo promise should not fulfill'); }),
            test4.step_func(function(e) {
                assert_equals(e, theError, 'pipeTo promise should reject with the write error');
                test4.done();
            })
        );
    });

    var test5 = async_test(label + ': piping with { preventClose: true } and a destination with that errors synchronously');
    test5.step(function() {
        var rs = factory();

        var theError = new Error('!!!');
        var ws = new WritableStream({
            close: function() {
                assert_unreached('unexpected close call');
            },
            abort: function() {
                assert_unreached('unexpected abort call');
            },
            write: function() {
                throw theError;
            }
        });

        rs.pipeTo(ws, { preventClose: true }).then(
            test5.step_func(function() { assert_unreached('pipeTo promise should not fulfill'); }),
            test5.step_func(function(e) {
                assert_equals(e, theError, 'pipeTo promise should reject with the write error');
                test5.done();
            })
        );
    });

    var test6 = async_test(label + ': piping with { preventClose: true } and a destination that errors on the last chunk');
    test6.step(function() {
        var rs = factory();

        var theError = new Error('!!!');
        var chunkCounter = 0;
        var ws = new WritableStream(
            {
                close: function() {
                    assert_unreached('unexpected close call');
                },
                abort: function() {
                    assert_unreached('unexpected abort call');
                },
                write: function() {
                    if (++chunkCounter === 2) {
                        return new Promise(test6.step_func(function(r, reject) { setTimeout(test6.step_func(function() { reject(theError); }), 200); }));
                    }
                }
            },
            {
                highWaterMark: Infinity,
                size: function() { return 1; }
            }
        );

        rs.pipeTo(ws, { preventClose: true }).then(
            test6.step_func(function() { assert_unreached('pipeTo promise should not fulfill'); }),
            test6.step_func(function(e) {
                assert_equals(e, theError, 'pipeTo promise should reject with the write error');
                test6.done();
            })
        );
    });
};

templatedRSClosed('ReadableStream (closed via call in start)', function() {
    return new ReadableStream({
        start: function(c) {
            c.close();
        }
    });
});

templatedRSClosed('ReadableStream (closed via cancel)', function() {
    var stream = new ReadableStream();
    stream.cancel();
    return stream;
});
var theError = new Error('boo!');

templatedRSErrored('ReadableStream (errored via call in start)', function() {
    return new ReadableStream({
        start: function(c) {
            c.error(theError);
        }
    })},
    theError
);

templatedRSErrored('ReadableStream (errored via returning a rejected promise in start)', function() {
    return new ReadableStream({
        start: function() {
            return Promise.reject(theError);
        }
    })},
    theError
);

templatedRSErroredAsyncOnly('ReadableStream (errored via returning a rejected promise in start) reader', function() {
    return new ReadableStream({
        start: function() { return Promise.reject(theError); }
    })},
    theError
);

var chunks = ['a', 'b'];

templatedRSTwoChunksClosed('ReadableStream (two chunks enqueued, then closed)', function() {
    return new ReadableStream({
        start: function(c) {
            c.enqueue(chunks[0]);
            c.enqueue(chunks[1]);
            c.close();
        }
    })},
    chunks
);

templatedRSTwoChunksClosed('ReadableStream (two chunks enqueued async, then closed)', function() {
    return new ReadableStream({
        _cancelled: false,
        start: function(c) {
            setTimeout(() => {
                if (!this._cancelled)
                    c.enqueue(chunks[0]);
            }, 100);
            setTimeout(() => {
                if (!this._cancelled)
                    c.enqueue(chunks[1]);
            }, 200);
            setTimeout(() => {
                if (!this._cancelled)
                    c.close();
            }, 300);
        },
        cancel: function(reason) {
             this._cancelled = true;
        }
    })},
    chunks
);

templatedRSTwoChunksClosed('ReadableStream (two chunks enqueued via pull, then closed)', function() {
    var pullCall = 0;

    return new ReadableStream({
        pull:function(c) {
            if (pullCall >= chunks.length) {
                c.close();
            } else {
                c.enqueue(chunks[pullCall++]);
            }
        }
    });
},
chunks
);
</script>
