Bug 1528981 [wpt PR 15463] - Add missing tests for ReadableStream, a=testonly
[gecko.git] / testing / web-platform / tests / streams / readable-streams / default-reader.any.js
blob224cd834b31f86174da9cd7d28048b7ab8689b2a
1 // META: global=worker,jsshell
2 // META: script=../resources/rs-utils.js
3 'use strict';
5 let ReadableStreamDefaultReader;
7 test(() => {
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');
14 test(() => {
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');
22 test(() => {
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');
40   }
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');
60 test(() => {
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');
67 test(() => {
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 ' +
75    'construction)');
77 test(() => {
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 ' +
84    'construction)');
86 test(() => {
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)');
94 test(() => {
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)');
102 test(() => {
104   const rs = new ReadableStream({
105     start(c) {
106       c.close();
107     }
108   });
110   new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
112 }, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is closed');
114 test(() => {
116   const theError = new Error('don\'t say i didn\'t warn ya');
117   const rs = new ReadableStream({
118     start(c) {
119       c.error(theError);
120     }
121   });
123   new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
125 }, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is errored');
127 promise_test(() => {
129   let controller;
130   const rs = new ReadableStream({
131     start(c) {
132       controller = c;
133     }
134   });
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');
139   });
141   controller.enqueue('a');
142   return promise;
144 }, 'Reading from a reader for an empty stream will wait until a chunk is available');
146 promise_test(() => {
148   let cancelCalled = false;
149   const passedReason = new Error('it wasn\'t the right time, sorry');
150   const rs = new ReadableStream({
151     cancel(reason) {
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');
155       cancelCalled = true;
156     }
157   });
159   const reader = rs.getReader();
160   return reader.cancel(passedReason).then(() => assert_true(cancelCalled));
162 }, 'cancel() on a reader does not release the reader');
164 promise_test(() => {
166   let controller;
167   const rs = new ReadableStream({
168     start(c) {
169       controller = c;
170     }
171   });
173   const reader = rs.getReader();
174   const promise = reader.closed;
176   controller.close();
177   return promise;
179 }, 'closed should be fulfilled after stream is closed (.closed access before acquiring)');
181 promise_test(t => {
183   let controller;
184   const rs = new ReadableStream({
185     start(c) {
186       controller = c;
187     }
188   });
190   const reader1 = rs.getReader();
192   reader1.releaseLock();
194   const reader2 = rs.getReader();
195   controller.close();
197   return Promise.all([
198     promise_rejects(t, new TypeError(), reader1.closed),
199     reader2.closed
200   ]);
202 }, 'closed should be rejected after reader releases its lock (multiple stream locks)');
204 promise_test(() => {
206   const rs = new ReadableStream({
207     start(c) {
208       c.enqueue('a');
209       c.enqueue('b');
210       c.close();
211     }
212   });
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');
217   });
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');
223   });
224   reader2.releaseLock();
226   return Promise.all([promise1, promise2]);
228 }, 'Multiple readers can access the stream in sequence');
230 promise_test(() => {
231   const rs = new ReadableStream({
232     start(c) {
233       c.enqueue('a');
234     }
235   });
237   const reader1 = rs.getReader();
238   reader1.releaseLock();
240   const reader2 = rs.getReader();
242   // Should be a no-op
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');
248   });
250 }, 'Cannot use an already-released reader to unlock a stream again');
252 promise_test(t => {
254   const rs = new ReadableStream({
255     start(c) {
256       c.enqueue('a');
257     },
258     cancel() {
259       assert_unreached('underlying source cancel should not be called');
260     }
261   });
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');
270   });
272   return Promise.all([
273     promise_rejects(t, new TypeError(), cancelPromise),
274     readPromise
275   ]);
277 }, 'cancel() on a released reader is a no-op and does not pass through');
279 promise_test(t => {
281   const promiseAsserts = [];
283   let controller;
284   const theError = { name: 'unique error' };
285   const rs = new ReadableStream({
286     start(c) {
287       controller = c;
288     }
289   });
291   const reader1 = rs.getReader();
293   promiseAsserts.push(
294     promise_rejects(t, theError, reader1.closed),
295     promise_rejects(t, theError, reader1.read())
296   );
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();
306   promiseAsserts.push(
307     promise_rejects(t, theError, reader2.closed),
308     promise_rejects(t, theError, reader2.read())
309   );
311   return Promise.all(promiseAsserts);
313 }, 'Getting a second reader after erroring the stream and releasing the reader should succeed');
315 promise_test(t => {
317   let controller;
318   const rs = new ReadableStream({
319     start(c) {
320       controller = c;
321     }
322   });
324   const promise = rs.getReader().closed.then(
325     t.unreached_func('closed promise should not be fulfilled when stream is errored'),
326     err => {
327       assert_equals(err, undefined, 'passed error should be undefined as it was');
328     }
329   );
331   controller.error();
332   return promise;
334 }, 'ReadableStreamDefaultReader closed promise should be rejected with undefined if that is the error');
337 promise_test(t => {
339   const rs = new ReadableStream({
340     start() {
341       return Promise.reject();
342     }
343   });
345   return rs.getReader().read().then(
346     t.unreached_func('read promise should not be fulfilled when stream is errored'),
347     err => {
348       assert_equals(err, undefined, 'passed error should be undefined as it was');
349     }
350   );
352 }, 'ReadableStreamDefaultReader: if start rejects with no parameter, it should error the stream with an undefined ' +
353     'error');
355 promise_test(t => {
357   const theError = { name: 'unique string' };
358   let controller;
359   const rs = new ReadableStream({
360     start(c) {
361       controller = c;
362     }
363   });
365   const promise = promise_rejects(t, theError, rs.getReader().closed);
367   controller.error(theError);
368   return promise;
370 }, 'Erroring a ReadableStream after checking closed should reject ReadableStreamDefaultReader closed promise');
372 promise_test(t => {
374   const theError = { name: 'unique string' };
375   let controller;
376   const rs = new ReadableStream({
377     start(c) {
378       controller = c;
379     }
380   });
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');
391 promise_test(() => {
393   let controller;
394   const rs = new ReadableStream({
395     start(c) {
396       controller = c;
397     }
398   });
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)');
404     }),
405     reader.read().then(result => {
406       assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
407     }),
408     reader.closed
409   ]);
411   controller.close();
412   return promise;
414 }, 'Reading twice on a stream that gets closed');
416 promise_test(() => {
418   let controller;
419   const rs = new ReadableStream({
420     start(c) {
421       controller = c;
422     }
423   });
425   controller.close();
426   const reader = rs.getReader();
428   return Promise.all([
429     reader.read().then(result => {
430       assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
431     }),
432     reader.read().then(result => {
433       assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
434     }),
435     reader.closed
436   ]);
438 }, 'Reading twice on a closed stream');
440 promise_test(t => {
442   let controller;
443   const rs = new ReadableStream({
444     start(c) {
445       controller = c;
446     }
447   });
449   const myError = { name: 'mashed potatoes' };
450   controller.error(myError);
452   const reader = rs.getReader();
454   return Promise.all([
455     promise_rejects(t, myError, reader.read()),
456     promise_rejects(t, myError, reader.read()),
457     promise_rejects(t, myError, reader.closed)
458   ]);
460 }, 'Reading twice on an errored stream');
462 promise_test(t => {
464   let controller;
465   const rs = new ReadableStream({
466     start(c) {
467       controller = c;
468     }
469   });
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)
478   ]);
480   controller.error(myError);
481   return promise;
483 }, 'Reading twice on a stream that gets errored');
485 test(() => {
486   const rs = new ReadableStream();
487   let toStringCalled = false;
488   const mode = {
489     toString() {
490       toStringCalled = true;
491       return '';
492     }
493   };
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');
498 promise_test(() => {
499   const rs = new ReadableStream({
500     pull(controller) {
501       controller.close();
502     }
503   });
505   const reader = rs.getReader();
506   return reader.read().then(() => {
507     // The test passes if releaseLock() does not throw.
508     reader.releaseLock();
509   });
510 }, 'controller.close() should clear the list of pending read requests');