1 // META: global=worker,jsshell
2 // META: script=../resources/rs-utils.js
5 let ReadableStreamDefaultReader;
9 // It's not exposed globally, but we test a few of its properties here.
10 ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor;
12 }, 'Can get the ReadableStreamDefaultReader constructor indirectly');
16 assert_throws(new TypeError(), () => new ReadableStreamDefaultReader('potato'));
17 assert_throws(new TypeError(), () => new ReadableStreamDefaultReader({}));
18 assert_throws(new TypeError(), () => new ReadableStreamDefaultReader());
20 }, 'ReadableStreamDefaultReader constructor should get a ReadableStream object as argument');
24 const methods = ['cancel', 'constructor', 'read', 'releaseLock'];
25 const properties = methods.concat(['closed']).sort();
27 const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
28 const proto = Object.getPrototypeOf(rsReader);
30 assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
32 for (const m of methods) {
33 const propDesc = Object.getOwnPropertyDescriptor(proto, m);
34 assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
35 assert_equals(propDesc.configurable, true, 'method should be configurable');
36 assert_equals(propDesc.writable, true, 'method should be writable');
37 assert_equals(typeof rsReader[m], 'function', 'should have be a method');
38 const expectedName = m === 'constructor' ? 'ReadableStreamDefaultReader' : m;
39 assert_equals(rsReader[m].name, expectedName, 'method should have the correct name');
42 const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed');
43 assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable');
44 assert_equals(closedPropDesc.configurable, true, 'closed should be configurable');
45 assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter');
46 assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter');
48 assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter');
49 assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property');
50 assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable');
51 assert_equals(typeof rsReader.constructor, 'function', 'has a constructor method');
52 assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter');
53 assert_equals(typeof rsReader.read, 'function', 'has a getReader method');
54 assert_equals(rsReader.read.length, 0, 'read has no parameters');
55 assert_equals(typeof rsReader.releaseLock, 'function', 'has a releaseLock method');
56 assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters');
58 }, 'ReadableStreamDefaultReader instances should have the correct list of properties');
62 const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
63 assert_equals(rsReader.closed, rsReader.closed, 'closed should return the same promise');
65 }, 'ReadableStreamDefaultReader closed should always return the same promise object');
69 const rs = new ReadableStream();
70 new ReadableStreamDefaultReader(rs); // Constructing directly the first time should be fine.
71 assert_throws(new TypeError(), () => new ReadableStreamDefaultReader(rs),
72 'constructing directly the second time should fail');
74 }, 'Constructing a ReadableStreamDefaultReader directly should fail if the stream is already locked (via direct ' +
79 const rs = new ReadableStream();
80 new ReadableStreamDefaultReader(rs); // Constructing directly should be fine.
81 assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail');
83 }, 'Getting a ReadableStreamDefaultReader via getReader should fail if the stream is already locked (via direct ' +
88 const rs = new ReadableStream();
89 rs.getReader(); // getReader() should be fine.
90 assert_throws(new TypeError(), () => new ReadableStreamDefaultReader(rs), 'constructing directly should fail');
92 }, 'Constructing a ReadableStreamDefaultReader directly should fail if the stream is already locked (via getReader)');
96 const rs = new ReadableStream();
97 rs.getReader(); // getReader() should be fine.
98 assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail');
100 }, 'Getting a ReadableStreamDefaultReader via getReader should fail if the stream is already locked (via getReader)');
104 const rs = new ReadableStream({
110 new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
112 }, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is closed');
116 const theError = new Error('don\'t say i didn\'t warn ya');
117 const rs = new ReadableStream({
123 new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
125 }, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is errored');
130 const rs = new ReadableStream({
135 const reader = rs.getReader();
137 const promise = reader.read().then(result => {
138 assert_object_equals(result, { value: 'a', done: false }, 'read() should fulfill with the enqueued chunk');
141 controller.enqueue('a');
144 }, 'Reading from a reader for an empty stream will wait until a chunk is available');
148 let cancelCalled = false;
149 const passedReason = new Error('it wasn\'t the right time, sorry');
150 const rs = new ReadableStream({
152 assert_true(rs.locked, 'the stream should still be locked');
153 assert_throws(new TypeError(), () => rs.getReader(), 'should not be able to get another reader');
154 assert_equals(reason, passedReason, 'the cancellation reason is passed through to the underlying source');
159 const reader = rs.getReader();
160 return reader.cancel(passedReason).then(() => assert_true(cancelCalled));
162 }, 'cancel() on a reader does not release the reader');
167 const rs = new ReadableStream({
173 const reader = rs.getReader();
174 const promise = reader.closed;
179 }, 'closed should be fulfilled after stream is closed (.closed access before acquiring)');
184 const rs = new ReadableStream({
190 const reader1 = rs.getReader();
192 reader1.releaseLock();
194 const reader2 = rs.getReader();
198 promise_rejects(t, new TypeError(), reader1.closed),
202 }, 'closed should be rejected after reader releases its lock (multiple stream locks)');
206 const rs = new ReadableStream({
214 const reader1 = rs.getReader();
215 const promise1 = reader1.read().then(r => {
216 assert_object_equals(r, { value: 'a', done: false }, 'reading the first chunk from reader1 works');
218 reader1.releaseLock();
220 const reader2 = rs.getReader();
221 const promise2 = reader2.read().then(r => {
222 assert_object_equals(r, { value: 'b', done: false }, 'reading the second chunk from reader2 works');
224 reader2.releaseLock();
226 return Promise.all([promise1, promise2]);
228 }, 'Multiple readers can access the stream in sequence');
231 const rs = new ReadableStream({
237 const reader1 = rs.getReader();
238 reader1.releaseLock();
240 const reader2 = rs.getReader();
243 reader1.releaseLock();
245 return reader2.read().then(result => {
246 assert_object_equals(result, { value: 'a', done: false },
247 'read() should still work on reader2 even after reader1 is released');
250 }, 'Cannot use an already-released reader to unlock a stream again');
254 const rs = new ReadableStream({
259 assert_unreached('underlying source cancel should not be called');
263 const reader = rs.getReader();
264 reader.releaseLock();
265 const cancelPromise = reader.cancel();
267 const reader2 = rs.getReader();
268 const readPromise = reader2.read().then(r => {
269 assert_object_equals(r, { value: 'a', done: false }, 'a new reader should be able to read a chunk');
273 promise_rejects(t, new TypeError(), cancelPromise),
277 }, 'cancel() on a released reader is a no-op and does not pass through');
281 const promiseAsserts = [];
284 const theError = { name: 'unique error' };
285 const rs = new ReadableStream({
291 const reader1 = rs.getReader();
294 promise_rejects(t, theError, reader1.closed),
295 promise_rejects(t, theError, reader1.read())
298 assert_throws(new TypeError(), () => rs.getReader(), 'trying to get another reader before erroring should throw');
300 controller.error(theError);
302 reader1.releaseLock();
304 const reader2 = rs.getReader();
307 promise_rejects(t, theError, reader2.closed),
308 promise_rejects(t, theError, reader2.read())
311 return Promise.all(promiseAsserts);
313 }, 'Getting a second reader after erroring the stream and releasing the reader should succeed');
318 const rs = new ReadableStream({
324 const promise = rs.getReader().closed.then(
325 t.unreached_func('closed promise should not be fulfilled when stream is errored'),
327 assert_equals(err, undefined, 'passed error should be undefined as it was');
334 }, 'ReadableStreamDefaultReader closed promise should be rejected with undefined if that is the error');
339 const rs = new ReadableStream({
341 return Promise.reject();
345 return rs.getReader().read().then(
346 t.unreached_func('read promise should not be fulfilled when stream is errored'),
348 assert_equals(err, undefined, 'passed error should be undefined as it was');
352 }, 'ReadableStreamDefaultReader: if start rejects with no parameter, it should error the stream with an undefined ' +
357 const theError = { name: 'unique string' };
359 const rs = new ReadableStream({
365 const promise = promise_rejects(t, theError, rs.getReader().closed);
367 controller.error(theError);
370 }, 'Erroring a ReadableStream after checking closed should reject ReadableStreamDefaultReader closed promise');
374 const theError = { name: 'unique string' };
376 const rs = new ReadableStream({
382 controller.error(theError);
384 // Let's call getReader twice for extra test coverage of this code path.
385 rs.getReader().releaseLock();
387 return promise_rejects(t, theError, rs.getReader().closed);
389 }, 'Erroring a ReadableStream before checking closed should reject ReadableStreamDefaultReader closed promise');
394 const rs = new ReadableStream({
399 const reader = rs.getReader();
401 const promise = Promise.all([
402 reader.read().then(result => {
403 assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
405 reader.read().then(result => {
406 assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
414 }, 'Reading twice on a stream that gets closed');
419 const rs = new ReadableStream({
426 const reader = rs.getReader();
429 reader.read().then(result => {
430 assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
432 reader.read().then(result => {
433 assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
438 }, 'Reading twice on a closed stream');
443 const rs = new ReadableStream({
449 const myError = { name: 'mashed potatoes' };
450 controller.error(myError);
452 const reader = rs.getReader();
455 promise_rejects(t, myError, reader.read()),
456 promise_rejects(t, myError, reader.read()),
457 promise_rejects(t, myError, reader.closed)
460 }, 'Reading twice on an errored stream');
465 const rs = new ReadableStream({
471 const myError = { name: 'mashed potatoes' };
472 const reader = rs.getReader();
474 const promise = Promise.all([
475 promise_rejects(t, myError, reader.read()),
476 promise_rejects(t, myError, reader.read()),
477 promise_rejects(t, myError, reader.closed)
480 controller.error(myError);
483 }, 'Reading twice on a stream that gets errored');
486 const rs = new ReadableStream();
487 let toStringCalled = false;
490 toStringCalled = true;
494 assert_throws(new RangeError(), () => rs.getReader({ mode }), 'getReader() should throw');
495 assert_true(toStringCalled, 'toString() should be called');
496 }, 'getReader() should call ToString() on mode');
499 const rs = new ReadableStream({
505 const reader = rs.getReader();
506 return reader.read().then(() => {
507 // The test passes if releaseLock() does not throw.
508 reader.releaseLock();
510 }, 'controller.close() should clear the list of pending read requests');