// META: global=window,worker
// META: script=../resources/test-utils.js
// META: script=../resources/recording-streams.js
'use strict';

const error1 = { name: 'error1' };

promise_test(() => {
  let resolveStartPromise;
  const ws = recordingWritableStream({
    start() {
      return new Promise(resolve => {
        resolveStartPromise = resolve;
      });
    }
  });

  const writer = ws.getWriter();

  assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
  writer.write('a');
  assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after writer.write()');

  // Wait and verify that write isn't called.
  return flushAsyncEvents()
      .then(() => {
        assert_array_equals(ws.events, [], 'write should not be called until start promise resolves');
        resolveStartPromise();
        return writer.ready;
      })
      .then(() => assert_array_equals(ws.events, ['write', 'a'],
                                      'write should not be called until start promise resolves'));
}, 'underlying sink\'s write should not be called until start finishes');

promise_test(() => {
  let resolveStartPromise;
  const ws = recordingWritableStream({
    start() {
      return new Promise(resolve => {
        resolveStartPromise = resolve;
      });
    }
  });

  const writer = ws.getWriter();

  writer.close();
  assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');

  // Wait and verify that write isn't called.
  return flushAsyncEvents().then(() => {
    assert_array_equals(ws.events, [], 'close should not be called until start promise resolves');
    resolveStartPromise();
    return writer.closed;
  });
}, 'underlying sink\'s close should not be called until start finishes');

test(() => {
  const passedError = new Error('horrible things');

  let writeCalled = false;
  let closeCalled = false;
  assert_throws_exactly(passedError, () => {
    // recordingWritableStream cannot be used here because the exception in the
    // constructor prevents assigning the object to a variable.
    new WritableStream({
      start() {
        throw passedError;
      },
      write() {
        writeCalled = true;
      },
      close() {
        closeCalled = true;
      }
    });
  }, 'constructor should throw passedError');
  assert_false(writeCalled, 'write should not be called');
  assert_false(closeCalled, 'close should not be called');
}, 'underlying sink\'s write or close should not be called if start throws');

promise_test(() => {
  const ws = recordingWritableStream({
    start() {
      return Promise.reject();
    }
  });

  // Wait and verify that write or close aren't called.
  return flushAsyncEvents()
      .then(() => assert_array_equals(ws.events, [], 'write and close should not be called'));
}, 'underlying sink\'s write or close should not be invoked if the promise returned by start is rejected');

promise_test(t => {
  const ws = new WritableStream({
    start() {
      return {
        then(onFulfilled, onRejected) { onRejected(error1); }
      };
    }
  });
  return promise_rejects_exactly(t, error1, ws.getWriter().closed, 'closed promise should be rejected');
}, 'returning a thenable from start() should work');

promise_test(t => {
  const ws = recordingWritableStream({
    start(controller) {
      controller.error(error1);
    }
  });
  return promise_rejects_exactly(t, error1, ws.getWriter().write('a'), 'write() should reject with the error')
      .then(() => {
        assert_array_equals(ws.events, [], 'sink write() should not have been called');
      });
}, 'controller.error() during start should cause writes to fail');

promise_test(t => {
  let controller;
  let resolveStart;
  const ws = recordingWritableStream({
    start(c) {
      controller = c;
      return new Promise(resolve => {
        resolveStart = resolve;
      });
    }
  });
  const writer = ws.getWriter();
  const writePromise = writer.write('a');
  const closePromise = writer.close();
  controller.error(error1);
  resolveStart();
  return Promise.all([
    promise_rejects_exactly(t, error1, writePromise, 'write() should fail'),
    promise_rejects_exactly(t, error1, closePromise, 'close() should fail')
  ]).then(() => {
    assert_array_equals(ws.events, [], 'sink write() and close() should not have been called');
  });
}, 'controller.error() during async start should cause existing writes to fail');

promise_test(t => {
  const events = [];
  const promises = [];
  function catchAndRecord(promise, name) {
    promises.push(promise.then(t.unreached_func(`promise ${name} should not resolve`),
                               () => {
                                 events.push(name);
                               }));
  }
  const ws = new WritableStream({
    start() {
      return Promise.reject();
    }
  }, { highWaterMark: 0 });
  const writer = ws.getWriter();
  catchAndRecord(writer.ready, 'ready');
  catchAndRecord(writer.closed, 'closed');
  catchAndRecord(writer.write(), 'write');
  return Promise.all(promises)
      .then(() => {
        assert_array_equals(events, ['ready', 'write', 'closed'], 'promises should reject in standard order');
      });
}, 'when start() rejects, writer promises should reject in standard order');
