// META: global=window,worker,shadowrealm
// 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';
promise_test(() => {
const ws = new WritableStream({
close() {
return 'Hello';
}
});
const writer = ws.getWriter();
const closePromise = writer.close();
return closePromise.then(value => assert_equals(value, undefined, 'fulfillment value must be undefined'));
}, 'fulfillment value of writer.close() call must be undefined even if the underlying sink returns a non-undefined ' +
'value');
promise_test(() => {
let controller;
let resolveClose;
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
return new Promise(resolve => {
resolveClose = resolve;
});
}
});
const writer = ws.getWriter();
const closePromise = writer.close();
return flushAsyncEvents().then(() => {
controller.error(error1);
return flushAsyncEvents();
}).then(() => {
resolveClose();
return Promise.all([
closePromise,
writer.closed,
flushAsyncEvents().then(() => writer.closed)]);
});
}, 'when sink calls error asynchronously while sink close is in-flight, the stream should not become errored');
promise_test(() => {
let controller;
const passedError = new Error('error me');
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
controller.error(passedError);
}
});
const writer = ws.getWriter();
return writer.close().then(() => writer.closed);
}, 'when sink calls error synchronously while closing, the stream should not become errored');
promise_test(t => {
const ws = new WritableStream({
close() {
throw error1;
}
});
const writer = ws.getWriter();
return Promise.all([
writer.write('y'),
promise_rejects_exactly(t, error1, writer.close(), 'close() must reject with the error'),
promise_rejects_exactly(t, error1, writer.closed, 'closed must reject with the error')
]);
}, 'when the sink throws during close, and the close is requested while a write is still in-flight, the stream should ' +
'become errored during the close');
promise_test(() => {
const ws = new WritableStream({
write(chunk, controller) {
controller.error(error1);
return new Promise(() => {});
}
});
const writer = ws.getWriter();
writer.write('a');
return delay(0).then(() => {
writer.releaseLock();
});
}, 'releaseLock on a stream with a pending write in which the stream has been errored');
promise_test(() => {
let controller;
const ws = new WritableStream({
start(c) {
controller = c;
},
close() {
controller.error(error1);
return new Promise(() => {});
}
});
const writer = ws.getWriter();
writer.close();
return delay(0).then(() => {
writer.releaseLock();
});
}, 'releaseLock on a stream with a pending close in which controller.error() was called');
promise_test(() => {
const ws = recordingWritableStream();
const writer = ws.getWriter();
return writer.ready.then(() => {
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
writer.close();
assert_equals(writer.desiredSize, 1, 'desiredSize should be still 1');
return writer.ready.then(v => {
assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
assert_array_equals(ws.events, ['close'], 'write and abort should not be called');
});
});
}, 'when close is called on a WritableStream in writable state, ready should return a fulfilled promise');
promise_test(() => {
const ws = recordingWritableStream({
write() {
return new Promise(() => {});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
writer.write('a');
assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
let calledClose = false;
return Promise.all([
writer.ready.then(v => {
assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
assert_true(calledClose, 'ready should not be fulfilled before writer.close() is called');
assert_array_equals(ws.events, ['write', 'a'], 'sink abort() should not be called');
}),
flushAsyncEvents().then(() => {
writer.close();
calledClose = true;
})
]);
});
}, 'when close is called on a WritableStream in waiting state, ready promise should be fulfilled');
promise_test(() => {
let asyncCloseFinished = false;
const ws = recordingWritableStream({
close() {
return flushAsyncEvents().then(() => {
asyncCloseFinished = true;
});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
writer.write('a');
writer.close();
return writer.ready.then(v => {
assert_false(asyncCloseFinished, 'ready promise should be fulfilled before async close completes');
assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
assert_array_equals(ws.events, ['write', 'a', 'close'], 'sink abort() should not be called');
});
});
}, 'when close is called on a WritableStream in waiting state, ready should be fulfilled immediately even if close ' +
'takes a long time');
promise_test(t => {
const rejection = { name: 'letter' };
const ws = new WritableStream({
close() {
return {
then(onFulfilled, onRejected) { onRejected(rejection); }
};
}
});
return promise_rejects_exactly(t, rejection, ws.getWriter().close(), 'close() should return a rejection');
}, 'returning a thenable from close() should work');
promise_test(t => {
const ws = new WritableStream();
const writer = ws.getWriter();
return writer.ready.then(() => {
const closePromise = writer.close();
const closedPromise = writer.closed;
writer.releaseLock();
return Promise.all([
closePromise,
promise_rejects_js(t, TypeError, closedPromise, '.closed promise should be rejected')
]);
});
}, 'releaseLock() should not change the result of sync close()');
promise_test(t => {
const ws = new WritableStream({
close() {
return flushAsyncEvents();
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const closePromise = writer.close();
const closedPromise = writer.closed;
writer.releaseLock();
return Promise.all([
closePromise,
promise_rejects_js(t, TypeError, closedPromise, '.closed promise should be rejected')
]);
});
}, 'releaseLock() should not change the result of async close()');
promise_test(() => {
let resolveClose;
const ws = new WritableStream({
close() {
const promise = new Promise(resolve => {
resolveClose = resolve;
});
return promise;
}
});
const writer = ws.getWriter();
const closePromise = writer.close();
writer.releaseLock();
return delay(0).then(() => {
resolveClose();
return closePromise.then(() => {
assert_equals(ws.getWriter().desiredSize, 0, 'desiredSize should be 0');
});
});
}, 'close() should set state to CLOSED even if writer has detached');
promise_test(() => {
let resolveClose;
const ws = new WritableStream({
close() {
const promise = new Promise(resolve => {
resolveClose = resolve;
});
return promise;
}
});
const writer = ws.getWriter();
writer.close();
writer.releaseLock();
return delay(0).then(() => {
const abortingWriter = ws.getWriter();
const abortPromise = abortingWriter.abort();
abortingWriter.releaseLock();
resolveClose();
return abortPromise;
});
}, 'the promise returned by async abort during close should resolve');
// Though the order in which the promises are fulfilled or rejected is arbitrary, we're checking it for
// interoperability. We can change the order as long as we file bugs on all implementers to update to the latest tests
// to keep them interoperable.
promise_test(() => {
const ws = new WritableStream({});
const writer = ws.getWriter();
const closePromise = writer.close();
const events = [];
return Promise.all([
closePromise.then(() => {
events.push('closePromise');
}),
writer.closed.then(() => {
events.push('closed');
})
]).then(() => {
assert_array_equals(events, ['closePromise', 'closed'],
'promises must fulfill/reject in the expected order');
});
}, 'promises must fulfill/reject in the expected order on closure');
promise_test(() => {
const ws = new WritableStream({});
// Wait until the WritableStream starts so that the close() call gets processed. Otherwise, abort() will be
// processed without waiting for completion of the close().
return delay(0).then(() => {
const writer = ws.getWriter();
const closePromise = writer.close();
const abortPromise = writer.abort(error1);
const events = [];
return Promise.all([
closePromise.then(() => {
events.push('closePromise');
}),
abortPromise.then(() => {
events.push('abortPromise');
}),
writer.closed.then(() => {
events.push('closed');
})
]).then(() => {
assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
'promises must fulfill/reject in the expected order');
});
});
}, 'promises must fulfill/reject in the expected order on aborted closure');
promise_test(t => {
const ws = new WritableStream({
close() {
return Promise.reject(error1);
}
});
// Wait until the WritableStream starts so that the close() call gets processed.
return delay(0).then(() => {
const writer = ws.getWriter();
const closePromise = writer.close();
const abortPromise = writer.abort(error2);
const events = [];
closePromise.catch(() => events.push('closePromise'));
abortPromise.catch(() => events.push('abortPromise'));
writer.closed.catch(() => events.push('closed'));
return Promise.all([
promise_rejects_exactly(t, error1, closePromise,
'closePromise must reject with the error returned from the sink\'s close method'),
promise_rejects_exactly(t, error1, abortPromise,
'abortPromise must reject with the error returned from the sink\'s close method'),
promise_rejects_exactly(t, error2, writer.closed,
'writer.closed must reject with error2')
]).then(() => {
assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
'promises must fulfill/reject in the expected order');
});
});
}, 'promises must fulfill/reject in the expected order on aborted and errored closure');
promise_test(t => {
let resolveWrite;
let controller;
const ws = new WritableStream({
write(chunk, c) {
controller = c;
return new Promise(resolve => {
resolveWrite = resolve;
});
}
});
const writer = ws.getWriter();
return writer.ready.then(() => {
const writePromise = writer.write('c');
controller.error(error1);
const closePromise = writer.close();
let closeRejected = false;
closePromise.catch(() => {
closeRejected = true;
});
return flushAsyncEvents().then(() => {
assert_false(closeRejected);
resolveWrite();
return Promise.all([
writePromise,
promise_rejects_exactly(t, error1, closePromise, 'close() should reject')
]).then(() => {
assert_true(closeRejected);
});
});
});
}, 'close() should not reject until no sink methods are in flight');
promise_test(() => {
const ws = new WritableStream();
const writer1 = ws.getWriter();
return writer1.close().then(() => {
writer1.releaseLock();
const writer2 = ws.getWriter();
const ready = writer2.ready;
assert_equals(ready.constructor, Promise);
return ready;
});
}, 'ready promise should be initialised as fulfilled for a writer on a closed stream');
promise_test(() => {
const ws = new WritableStream();
ws.close();
const writer = ws.getWriter();
return writer.closed;
}, 'close() on a writable stream should work');
promise_test(t => {
const ws = new WritableStream();
ws.getWriter();
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
}, 'close() on a locked stream should reject');
promise_test(t => {
const ws = new WritableStream({
start(controller) {
controller.error(error1);
}
});
return promise_rejects_exactly(t, error1, ws.close(), 'close should reject with error1');
}, 'close() on an erroring stream should reject');
promise_test(t => {
const ws = new WritableStream({
start(controller) {
controller.error(error1);
}
});
const writer = ws.getWriter();
return promise_rejects_exactly(t, error1, writer.closed, 'closed should reject with the error').then(() => {
writer.releaseLock();
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
});
}, 'close() on an errored stream should reject');
promise_test(t => {
const ws = new WritableStream();
const writer = ws.getWriter();
return writer.close().then(() => {
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
});
}, 'close() on an closed stream should reject');
promise_test(t => {
const ws = new WritableStream({
close() {
return new Promise(() => {});
}
});
const writer = ws.getWriter();
writer.close();
writer.releaseLock();
return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
}, 'close() on a stream with a pending close should reject');