diff --git a/asynciterator.ts b/asynciterator.ts index 3ec2ab6..7bb32c9 100644 --- a/asynciterator.ts +++ b/asynciterator.ts @@ -98,19 +98,12 @@ export class AsyncIterator extends EventEmitter { @returns {boolean} Whether the state was changed @emits module:asynciterator.AsyncIterator.end */ - protected _changeState(newState: number, eventAsync = false) { + protected _changeState(newState: number) { // Validate the state change const valid = newState > this._state && this._state < ENDED; - if (valid) { + if (valid) this._state = newState; - // Emit the `end` event when changing to ENDED - if (newState === ENDED) { - if (!eventAsync) - this.emit('end'); - else - taskScheduler(() => this.emit('end')); - } - } + return valid; } @@ -128,6 +121,8 @@ export class AsyncIterator extends EventEmitter { @returns {object?} The next item, or `null` if none is available */ read(): T | null { + if (this._state === CLOSED) + this._end(); return null; } @@ -170,8 +165,12 @@ export class AsyncIterator extends EventEmitter { @emits module:asynciterator.AsyncIterator.end */ close() { - if (this._changeState(CLOSED)) - this._endAsync(); + if (this._changeState(CLOSED)) { + if (this.readable) + emitData.call(this); + else + this.readable = true; + } } /** @@ -216,6 +215,8 @@ export class AsyncIterator extends EventEmitter { */ protected _end(destroy = false) { if (this._changeState(destroy ? DESTROYED : ENDED)) { + if (!destroy) + this.emit('end'); this._readable = false; this.removeAllListeners('readable'); this.removeAllListeners('data'); @@ -223,14 +224,6 @@ export class AsyncIterator extends EventEmitter { } } - /** - Asynchronously calls `_end`. - @protected - */ - protected _endAsync() { - taskScheduler(() => this._end()); - } - /** The `end` event is emitted after the last item of the iterator has been read. @event module:asynciterator.end @@ -432,7 +425,7 @@ export class AsyncIterator extends EventEmitter { After this operation, only read the returned iterator instead of the current one. @param {object|Function} [options] Settings of the iterator, or the transformation function @param {integer} [options.maxbufferSize=4] The maximum number of items to keep in the buffer - @param {boolean} [options.autoStart=true] Whether buffering starts directly after construction + @param {boolean} [options.preBuffer=true] Whether buffering starts directly after construction @param {integer} [options.offset] The number of items to skip @param {integer} [options.limit] The maximum number of items @param {Function} [options.filter] A function to synchronously filter items from the source @@ -583,16 +576,16 @@ export class EmptyIterator extends AsyncIterator { /** Creates a new `EmptyIterator`. */ constructor() { super(); - this._changeState(ENDED, true); + this.close(); } } /** An iterator that emits a single item. - @extends module:asynciterator.AsyncIterator + @extends module:asynciterator.EmptyIterator */ -export class SingletonIterator extends AsyncIterator { +export class SingletonIterator extends EmptyIterator { private _item: T | null; /** @@ -602,17 +595,14 @@ export class SingletonIterator extends AsyncIterator { constructor(item: T) { super(); this._item = item; - if (item === null) - this.close(); - else - this.readable = true; } /* Reads the item from the iterator. */ read() { const item = this._item; this._item = null; - this.close(); + if (item === null) + this._end(); return item; } @@ -625,43 +615,33 @@ export class SingletonIterator extends AsyncIterator { /** An iterator that emits the items of a given array. - @extends module:asynciterator.AsyncIterator + @extends module:asynciterator.EmptyIterator */ -export class ArrayIterator extends AsyncIterator { +export class ArrayIterator extends EmptyIterator { private _buffer?: T[]; - protected _sourceStarted: boolean; /** Creates a new `ArrayIterator`. @param {Array} items The items that will be emitted. */ - constructor(items?: Iterable, { autoStart = true } = {}) { + constructor(items: Iterable) { super(); - const buffer = items ? [...items] : []; - this._sourceStarted = autoStart !== false; - if (this._sourceStarted && buffer.length === 0) - this.close(); - else + const buffer = [...items]; + if (buffer.length !== 0) this._buffer = buffer; - this.readable = true; } /* Reads an item from the iterator. */ read() { - if (!this._sourceStarted) - this._sourceStarted = true; - - let item = null; - const buffer = this._buffer; - if (buffer) { - if (buffer.length !== 0) - item = buffer.shift() as T; - if (buffer.length === 0) { - delete this._buffer; - this.close(); - } + const { _buffer } = this; + if (!_buffer) { + this._end(); + return null; } - return item; + if (_buffer.length === 1) + delete this._buffer; + + return _buffer.shift() as T; } /* Generates details for a textual representation of the iterator. */ @@ -725,8 +705,10 @@ export class IntegerIterator extends AsyncIterator { /* Reads an item from the iterator. */ read() { - if (this.closed) + if (this.closed) { + this._end(); return null; + } const current = this._next, step = this._step, last = this._last, next = this._next += step; if (step >= 0 ? next > last : next < last) @@ -758,13 +740,13 @@ export class BufferedIterator extends AsyncIterator { Creates a new `BufferedIterator`. @param {object} [options] Settings of the iterator @param {integer} [options.maxBufferSize=4] The number of items to preload in the internal buffer - @param {boolean} [options.autoStart=true] Whether buffering starts directly after construction + @param {boolean} [options.preBuffer=true] Whether buffering starts directly after construction */ - constructor({ maxBufferSize = 4, autoStart = true } = {}) { + constructor({ maxBufferSize = 4, preBuffer = true } = {}) { super(INIT); this.maxBufferSize = maxBufferSize; - taskScheduler(() => this._init(autoStart)); - this._sourceStarted = autoStart !== false; + taskScheduler(() => this._init(preBuffer)); + this._sourceStarted = preBuffer !== false; } /** @@ -796,9 +778,9 @@ export class BufferedIterator extends AsyncIterator { Initializing the iterator by calling {@link BufferedIterator#_begin} and changing state from INIT to OPEN. @protected - @param {boolean} autoStart Whether reading of items should immediately start after OPEN. + @param {boolean} preBuffer Whether reading of items should immediately start after OPEN. */ - protected _init(autoStart: boolean) { + protected _init(preBuffer: boolean) { // Perform initialization tasks let doneCalled = false; this._reading = true; @@ -809,7 +791,7 @@ export class BufferedIterator extends AsyncIterator { // Open the iterator and start buffering this._reading = false; this._changeState(OPEN); - if (autoStart) + if (preBuffer) this._fillBufferAsync(); // If reading should not start automatically, the iterator doesn't become readable. // Therefore, mark the iterator as (potentially) readable so consumers know it might be read. @@ -850,6 +832,8 @@ export class BufferedIterator extends AsyncIterator { item = buffer.shift() as T; } else { + if (this._state === CLOSED) + this._end(); item = null; this.readable = false; } @@ -859,9 +843,6 @@ export class BufferedIterator extends AsyncIterator { // If the iterator is not closed and thus may still generate new items, fill the buffer if (!this.closed) this._fillBufferAsync(); - // No new items will be generated, so if none are buffered, the iterator ends here - else if (!buffer.length) - this._endAsync(); } return item; @@ -976,17 +957,15 @@ export class BufferedIterator extends AsyncIterator { @emits module:asynciterator.AsyncIterator.end */ protected _completeClose() { - if (this._changeState(CLOSED)) { + if (this._state < CLOSED) { // Write possible terminating items this._reading = true; this._flush(() => { if (!this._reading) throw new Error('done callback called multiple times'); this._reading = false; - // If no items are left, end the iterator - // Otherwise, `read` becomes responsible for ending the iterator - if (!this._buffer.length) - this._endAsync(); + // If no items are left, close the iterator + AsyncIterator.prototype.close.call(this); }); } } @@ -1035,7 +1014,7 @@ export class TransformIterator extends BufferedIterator { @param {module:asynciterator.AsyncIterator|Readable} [source] The source this iterator generates items from @param {object} [options] Settings of the iterator @param {integer} [options.maxBufferSize=4] The maximum number of items to keep in the buffer - @param {boolean} [options.autoStart=true] Whether buffering starts directly after construction + @param {boolean} [options.preBuffer=true] Whether buffering starts directly after construction @param {boolean} [options.optional=false] If transforming is optional, the original item is pushed when its transformation yields no items @param {boolean} [options.destroySource=true] Whether the source should be destroyed when this transformed iterator is closed or destroyed @param {module:asynciterator.AsyncIterator} [options.source] The source this iterator generates items from @@ -1233,7 +1212,7 @@ export class SimpleTransformIterator extends TransformIterator { @param {module:asynciterator.AsyncIterator|Readable} [source] The source this iterator generates items from @param {object|Function} [options] Settings of the iterator, or the transformation function @param {integer} [options.maxbufferSize=4] The maximum number of items to keep in the buffer - @param {boolean} [options.autoStart=true] Whether buffering starts directly after construction + @param {boolean} [options.preBuffer=true] Whether buffering starts directly after construction @param {module:asynciterator.AsyncIterator} [options.source] The source this iterator generates items from @param {integer} [options.offset] The number of items to skip @param {integer} [options.limit] The maximum number of items @@ -1375,7 +1354,7 @@ export class MultiTransformIterator extends TransformIterator { @param {module:asynciterator.AsyncIterator|Readable} [source] The source this iterator generates items from @param {object|Function} [options] Settings of the iterator, or the transformation function @param {integer} [options.maxbufferSize=4] The maximum number of items to keep in the buffer - @param {boolean} [options.autoStart=true] Whether buffering starts directly after construction + @param {boolean} [options.preBuffer=true] Whether buffering starts directly after construction @param {module:asynciterator.AsyncIterator} [options.source] The source this iterator generates items from @param {integer} [options.offset] The number of items to skip @param {integer} [options.limit] The maximum number of items @@ -1405,15 +1384,29 @@ export class MultiTransformIterator extends TransformIterator { // Remove transformers that have ended const transformerQueue = this._transformerQueue, optional = this._optional; let head, item; - while ((head = transformerQueue[0]) && head.transformer.done) { + while ((head = transformerQueue[0]) !== undefined) { + const { transformer } = head; + while (count > 0 && (item = transformer.read()) !== null) { + count--; + this._push(item); + // If a transformed item was pushed, no need to push the original anymore + if (optional && head.item !== null) + head.item = null; + } + + // If the transformer is not done, end this read so it has time to load items + if (!transformer.done) + break; + // If transforming is optional, push the original item if none was pushed if (optional && head.item !== null) { + if (count === 0) + break; + count--; this._push(head.item as any as D); } - // Remove listeners from the transformer transformerQueue.shift(); - const { transformer } = head; transformer.removeListener('end', destinationFillBuffer); transformer.removeListener('readable', destinationFillBuffer); transformer.removeListener('error', destinationEmitError); @@ -1436,21 +1429,10 @@ export class MultiTransformIterator extends TransformIterator { transformerQueue.push({ transformer, item }); } - // Try to read `count` items from the transformer - head = transformerQueue[0]; - if (head) { - const { transformer } = head; - while (count-- > 0 && (item = transformer.read()) !== null) { - this._push(item); - // If a transformed item was pushed, no need to push the original anymore - if (optional) - head.item = null; - } - } // End the iterator if the source has ended - else if (source && source.done) { + if (transformerQueue.length === 0 && source && source.done) this.close(); - } + done(); } @@ -1488,13 +1470,13 @@ export class UnionIterator extends BufferedIterator { constructor(sources: AsyncIteratorOrArray>, options: BufferedIteratorOptions = {}) { super(options); - const autoStart = options.autoStart !== false; + const preBuffer = options.preBuffer !== false; // Sources have been passed as an iterator if (isEventEmitter(sources)) { sources.on('error', error => this.emit('error', error)); this._pending = { sources }; - if (autoStart) + if (preBuffer) this._loadSources(); } // Sources have been passed as a non-empty array @@ -1503,7 +1485,7 @@ export class UnionIterator extends BufferedIterator { this._addSource(source as InternalSource); } // Sources are an empty list - else if (autoStart) { + else if (preBuffer) { this.close(); } } @@ -1600,7 +1582,7 @@ export class ClonedIterator extends TransformIterator { @param {module:asynciterator.AsyncIterator|Readable} [source] The source this iterator copies items from */ constructor(source: AsyncIterator) { - super(source, { autoStart: false }); + super(source, { preBuffer: false }); this._reading = false; } @@ -1626,7 +1608,7 @@ export class ClonedIterator extends TransformIterator { (source._destination = new HistoryReader(source) as any); // Close this clone if history is empty and the source has ended - if (history.endsAt(0)) { + if (history.endsAt(0) || (source as any)._state === CLOSED) { this.close(); } else { @@ -1714,9 +1696,13 @@ export class ClonedIterator extends TransformIterator { else this.readable = false; // Close the iterator if we are at the end of the source - if (history.endsAt(this._readPosition)) + if (history.endsAt(this._readPosition) || (source as any)._state === CLOSED) this.close(); } + + if (this._state === CLOSED && item === null) + this._end(false); + return item; } @@ -1885,7 +1871,7 @@ function isSourceExpression(object: any): object is SourceExpression { export interface BufferedIteratorOptions { maxBufferSize?: number; - autoStart?: boolean; + preBuffer?: boolean; } export interface TransformIteratorOptions extends BufferedIteratorOptions { diff --git a/package-lock.json b/package-lock.json index 831df0f..b048a40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "c8": "^7.2.0", "chai": "^4.2.0", "eslint": "^5.16.0", + "event-emitter-promisify": "^1.1.0", "husky": "^4.2.5", "jaguarjs-jsdoc": "^1.1.0", "jsdoc": "^3.5.5", @@ -2002,6 +2003,12 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter-promisify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/event-emitter-promisify/-/event-emitter-promisify-1.1.0.tgz", + "integrity": "sha512-uyHG8gjwYGDlKoo0Txtx/u1HI1ubj0FK0rVqI4O0s1EymQm4iAEMbrS5B+XFlSaS8SZ3xzoKX+YHRZk8Nk/bXg==", + "dev": true + }, "node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -7622,6 +7629,12 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "event-emitter-promisify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/event-emitter-promisify/-/event-emitter-promisify-1.1.0.tgz", + "integrity": "sha512-uyHG8gjwYGDlKoo0Txtx/u1HI1ubj0FK0rVqI4O0s1EymQm4iAEMbrS5B+XFlSaS8SZ3xzoKX+YHRZk8Nk/bXg==", + "dev": true + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", diff --git a/package.json b/package.json index 06ab110..ecf20a8 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "c8": "^7.2.0", "chai": "^4.2.0", "eslint": "^5.16.0", + "event-emitter-promisify": "^1.1.0", "husky": "^4.2.5", "jaguarjs-jsdoc": "^1.1.0", "jsdoc": "^3.5.5", diff --git a/test/ArrayIterator-test.js b/test/ArrayIterator-test.js index a7464aa..9300da5 100644 --- a/test/ArrayIterator-test.js +++ b/test/ArrayIterator-test.js @@ -5,12 +5,13 @@ import { } from '../dist/asynciterator.js'; import { EventEmitter } from 'events'; +import { promisifyEventEmitter } from 'event-emitter-promisify'; describe('ArrayIterator', () => { describe('The ArrayIterator function', () => { describe('the result when called with `new`', () => { let instance; - before(() => { instance = new ArrayIterator(); }); + before(() => { instance = new ArrayIterator([]); }); it('should be an ArrayIterator object', () => { instance.should.be.an.instanceof(ArrayIterator); @@ -27,7 +28,7 @@ describe('ArrayIterator', () => { describe('the result when called through `fromArray`', () => { let instance; - before(() => { instance = fromArray(); }); + before(() => { instance = fromArray([]); }); it('should be an ArrayIterator object', () => { instance.should.be.an.instanceof(ArrayIterator); @@ -46,7 +47,7 @@ describe('ArrayIterator', () => { describe('An ArrayIterator without arguments', () => { let iterator; before(() => { - iterator = new ArrayIterator(); + iterator = new ArrayIterator([]); captureEvents(iterator, 'readable', 'end'); }); @@ -54,8 +55,17 @@ describe('ArrayIterator', () => { iterator.toString().should.equal('[ArrayIterator (0)]'); }); - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -95,7 +105,16 @@ describe('ArrayIterator', () => { }); it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('data', () => { throw new Error('should not emit data'); }); + iterator.on('end', done); }); it('should have emitted the `end` event', () => { @@ -127,10 +146,127 @@ describe('ArrayIterator', () => { }); }); - describe('An ArrayIterator with an empty array without autoStart', () => { + describe('An ArrayIterator with an empty array (no use of flow)', () => { let iterator; before(() => { - iterator = new ArrayIterator([], { autoStart: false }); + iterator = new ArrayIterator([]); + captureEvents(iterator, 'readable', 'end'); + }); + + it('should provide a readable `toString` representation', () => { + iterator.toString().should.equal('[ArrayIterator (0)]'); + }); + + it('should not have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + + it('should have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(1); + }); + + it('should have ended', () => { + iterator.ended.should.be.true; + }); + + it('should not have been destroyed', () => { + iterator.destroyed.should.be.false; + }); + + it('should be done', () => { + iterator.done.should.be.true; + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + }); + + + describe('An ArrayIterator with an array [1] (no use of flow)', () => { + let iterator; + before(() => { + iterator = new ArrayIterator([1]); + captureEvents(iterator, 'readable', 'end'); + }); + + it('should provide a readable `toString` representation', () => { + iterator.toString().should.equal('[ArrayIterator (1)]'); + }); + + it('should not have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should return 1 when read is called', () => { + expect(iterator.read()).to.equal(1); + }); + + it('should not have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + + it('should have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(1); + }); + + it('should have ended', () => { + iterator.ended.should.be.true; + }); + + it('should not have been destroyed', () => { + iterator.destroyed.should.be.false; + }); + + it('should be done', () => { + iterator.done.should.be.true; + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + }); + + describe('An ArrayIterator with an empty array', () => { + let iterator; + before(() => { + iterator = new ArrayIterator([]); captureEvents(iterator, 'readable', 'end'); }); @@ -679,4 +815,56 @@ describe('ArrayIterator', () => { }); }); }); + + describe('A ArrayIterator with no elements should not emit until read from', () => { + it('awaiting undefined (with empty array)', async () => { + const iterator = new ArrayIterator([]); + await undefined; + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting promise (with empty array)', async () => { + const iterator = new ArrayIterator([]); + await Promise.resolve(); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting undefined (with one element)', async () => { + const iterator = new ArrayIterator([1]); + await undefined; + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting promise (with one element)', async () => { + const iterator = new ArrayIterator([1]); + await Promise.resolve(); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + }); + + describe('An ArrayIterator with no elements should not emit until read from (fromArray constructor)', () => { + it('awaiting undefined (with empty array)', async () => { + const iterator = fromArray([]); + await undefined; + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting promise (with empty array)', async () => { + const iterator = fromArray([]); + await Promise.resolve(); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting undefined (with one element)', async () => { + const iterator = fromArray([1]); + await undefined; + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting promise (with one element)', async () => { + const iterator = fromArray([1]); + await Promise.resolve(); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + }); }); diff --git a/test/AsyncIterator-test.js b/test/AsyncIterator-test.js index 37a74d6..58a470c 100644 --- a/test/AsyncIterator-test.js +++ b/test/AsyncIterator-test.js @@ -97,8 +97,8 @@ describe('AsyncIterator', () => { describe('after close has been called', () => { before(() => { iterator.close(); }); - it('should not have emitted another `readable` event', () => { - iterator._eventCounts.readable.should.equal(1); + it('should have emitted another `readable` event', () => { + iterator._eventCounts.readable.should.equal(2); }); it('should have emitted the `end` event', () => { @@ -141,7 +141,7 @@ describe('AsyncIterator', () => { before(() => { iterator.destroy(); }); it('should not have emitted another `readable` event', () => { - iterator._eventCounts.readable.should.equal(1); + iterator._eventCounts.readable.should.equal(2); }); it('should have emitted the `end` event', () => { @@ -184,7 +184,7 @@ describe('AsyncIterator', () => { before(() => { iterator.close(); }); it('should not have emitted another `readable` event', () => { - iterator._eventCounts.readable.should.equal(1); + iterator._eventCounts.readable.should.equal(2); }); it('should not have emitted the `end` event a second time', () => { @@ -639,13 +639,27 @@ describe('AsyncIterator', () => { }); }); - describe('after the iterator is closed', () => { + describe('after the iterator is closed ', () => { before(() => { iterator.close(); }); + it('should have closed', () => { + iterator._state.should.equal(CLOSED); + }); + }); + + describe('after the iterator is closed and then _end', () => { + before(() => { + iterator._end(); + }); + + it('should have ended', () => { + iterator._state.should.equal(ENDED); + }); + it('should not have listeners for the `data` event', () => { - EventEmitter.listenerCount(iterator, 'readable').should.equal(0); + EventEmitter.listenerCount(iterator, 'data').should.equal(0); }); it('should not be listening for the `readable` event', () => { @@ -681,7 +695,7 @@ describe('AsyncIterator', () => { }); it('should not have listeners for the `data` event', () => { - EventEmitter.listenerCount(iterator, 'readable').should.equal(0); + EventEmitter.listenerCount(iterator, 'data').should.equal(0); }); it('should not be listening for the `readable` event', () => { @@ -723,7 +737,7 @@ describe('AsyncIterator', () => { }); it('should not have listeners for the `data` event', () => { - EventEmitter.listenerCount(iterator, 'readable').should.equal(0); + EventEmitter.listenerCount(iterator, 'data').should.equal(0); }); it('should not be listening for the `readable` event', () => { @@ -1092,7 +1106,7 @@ describe('AsyncIterator', () => { before(done => { iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => iterator.close() || null; + iterator.read = () => iterator._end() || null; iterator.toArray().then(array => { result = array; done(); @@ -1110,7 +1124,7 @@ describe('AsyncIterator', () => { let i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 2 ? i : (iterator.close() || null); + iterator.read = () => i++ < 2 ? i : (iterator._end() || null); iterator.toArray().then(array => { result = array; done(); @@ -1147,7 +1161,7 @@ describe('AsyncIterator', () => { let i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 5 ? i : (iterator.close() || null); + iterator.read = () => i++ < 5 ? i : (iterator._end() || null); iterator.toArray({}).then(array => { result = array; done(); @@ -1165,7 +1179,7 @@ describe('AsyncIterator', () => { let i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 5 ? i : (iterator.close() || null); + iterator.read = () => i++ < 5 ? i : (iterator._end() || null); iterator.toArray(null).then(array => { result = array; done(); @@ -1183,7 +1197,7 @@ describe('AsyncIterator', () => { let i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 5 ? i : (iterator.close() || null); + iterator.read = () => i++ < 5 ? i : (iterator._end() || null); iterator.toArray({ limit: 0 }).then(array => { result = array; done(); @@ -1201,7 +1215,7 @@ describe('AsyncIterator', () => { let i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 5 ? i : (iterator.close() || null); + iterator.read = () => i++ < 5 ? i : (iterator._end() || null); iterator.toArray({ limit: -3 }).then(array => { result = array; done(); @@ -1219,7 +1233,7 @@ describe('AsyncIterator', () => { let i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 5 ? i : (iterator.close() || null); + iterator.read = () => i++ < 5 ? i : (iterator._end() || null); iterator.toArray({ limit: -0 }).then(array => { result = array; done(); @@ -1237,7 +1251,7 @@ describe('AsyncIterator', () => { let i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 5 ? i : (iterator.close() || null); + iterator.read = () => i++ < 5 ? i : (iterator._end() || null); iterator.toArray({ limit: '3' }).then(array => { result = array; done(); @@ -1255,7 +1269,7 @@ describe('AsyncIterator', () => { i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 5 ? i : (iterator.close() || null); + iterator.read = () => i++ < 5 ? i : (iterator._end() || null); iterator.toArray({ limit: 3 }).then(array => { result = array; done(); @@ -1295,7 +1309,7 @@ describe('AsyncIterator', () => { let i = 0; iterator = new AsyncIterator(); iterator.readable = true; - iterator.read = () => i++ < 5 ? i : (iterator.close() || null); + iterator.read = () => i++ < 5 ? i : (iterator._end() || null); iterator.toArray({ limit: 10 }).then(array => { result = array; done(); diff --git a/test/BufferedIterator-test.js b/test/BufferedIterator-test.js index ce59d48..2b583e0 100644 --- a/test/BufferedIterator-test.js +++ b/test/BufferedIterator-test.js @@ -75,6 +75,23 @@ describe('BufferedIterator', () => { iterator.close(); }); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -113,9 +130,9 @@ describe('BufferedIterator', () => { return captureEvents(iterator, 'readable', 'end'); } - describe('without autoStart', () => { + describe('without preBuffer', () => { let iterator; - before(() => { iterator = createIterator({ autoStart: false }); }); + before(() => { iterator = createIterator({ preBuffer: false }); }); describe('before `read` has been called', () => { it('should have emitted the `readable` event', () => { @@ -155,10 +172,24 @@ describe('BufferedIterator', () => { expect(item).to.be.null; }); - it('should not have emitted the `readable` event anymore', () => { - iterator._eventCounts.readable.should.equal(1); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(2); + }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -194,7 +225,7 @@ describe('BufferedIterator', () => { }); it('should not have emitted the `readable` event anymore', () => { - iterator._eventCounts.readable.should.equal(1); + iterator._eventCounts.readable.should.equal(2); }); it('should not have emitted another `end` event', () => { @@ -223,13 +254,30 @@ describe('BufferedIterator', () => { }); }); - describe('with autoStart', () => { + describe('with preBuffer', () => { let iterator; before(() => { iterator = createIterator(); }); describe('before `read` has been called', () => { - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; }); it('should have emitted the `end` event', () => { @@ -267,7 +315,7 @@ describe('BufferedIterator', () => { }); it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + iterator._eventCounts.readable.should.equal(1); }); it('should not have emitted another `end` event', () => { @@ -310,9 +358,9 @@ describe('BufferedIterator', () => { return captureEvents(iterator, 'readable', 'end'); } - describe('without autoStart', () => { + describe('without preBuffer', () => { let iterator; - before(() => { iterator = createIterator({ autoStart: false }); }); + before(() => { iterator = createIterator({ preBuffer: false }); }); describe('before `read` has been called', () => { it('should have emitted the `readable` event', () => { @@ -353,7 +401,24 @@ describe('BufferedIterator', () => { }); it('should not have emitted another `readable` event', () => { - iterator._eventCounts.readable.should.equal(1); + iterator._eventCounts.readable.should.equal(2); + }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; }); it('should have emitted the `end` event', () => { @@ -391,7 +456,7 @@ describe('BufferedIterator', () => { }); it('should not have emitted another `readable` event', () => { - iterator._eventCounts.readable.should.equal(1); + iterator._eventCounts.readable.should.equal(2); }); it('should not have emitted another `end` event', () => { @@ -420,13 +485,31 @@ describe('BufferedIterator', () => { }); }); - describe('with autoStart', () => { + describe('with preBuffer', () => { let iterator; before(() => { iterator = createIterator(); }); describe('before `read` has been called', () => { it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + iterator._eventCounts.readable.should.equal(1); + }); + + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; }); it('should have emitted the `end` event', () => { @@ -464,7 +547,7 @@ describe('BufferedIterator', () => { }); it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + iterator._eventCounts.readable.should.equal(1); }); it('should not have emitted another `end` event', () => { @@ -535,7 +618,24 @@ describe('BufferedIterator', () => { before(() => { iterator.close(); }); it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; }); it('should have emitted the `end` event', () => { @@ -563,7 +663,7 @@ describe('BufferedIterator', () => { describe('A BufferedIterator that is being closed while reading is in progress', () => { let iterator, _readDone; function createIterator() { - iterator = new BufferedIterator({ autoStart: false, maxBufferSize: 1 }); + iterator = new BufferedIterator({ preBuffer: false, maxBufferSize: 1 }); iterator._read = (count, done) => { _readDone = done; }; sinon.spy(iterator, '_read'); captureEvents(iterator, 'readable', 'end'); @@ -606,6 +706,24 @@ describe('BufferedIterator', () => { // because the iterator closes before an asynchronous _read can take place }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -664,6 +782,23 @@ describe('BufferedIterator', () => { scheduleTask(() => { iterator.close(); }); }); + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -839,6 +974,24 @@ describe('BufferedIterator', () => { iterator._eventCounts.readable.should.equal(2); }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -1014,6 +1167,23 @@ describe('BufferedIterator', () => { iterator._eventCounts.readable.should.equal(2); }); + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -1057,9 +1227,9 @@ describe('BufferedIterator', () => { return captureEvents(iterator, 'readable', 'end'); } - describe('without autoStart', () => { + describe('without preBuffer', () => { let iterator; - before(() => { iterator = createIterator({ autoStart: false }); }); + before(() => { iterator = createIterator({ preBuffer: false }); }); it('should provide a readable `toString` representation', () => { iterator.toString().should.equal('[BufferedIterator {buffer: 0}]'); @@ -1145,6 +1315,23 @@ describe('BufferedIterator', () => { iterator._eventCounts.readable.should.equal(2); }); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(2); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -1209,7 +1396,7 @@ describe('BufferedIterator', () => { }); }); - describe('with autoStart', () => { + describe('with preBuffer', () => { let iterator; before(() => { iterator = createIterator(); }); @@ -1260,6 +1447,23 @@ describe('BufferedIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -1302,7 +1506,7 @@ describe('BufferedIterator', () => { return captureEvents(iterator, 'readable', 'end'); } - describe('with autoStart enabled', () => { + describe('with preBuffer enabled', () => { let iterator; before(() => { iterator = createIterator(); }); @@ -1406,7 +1610,7 @@ describe('BufferedIterator', () => { describe('A BufferedIterator with `_read` that calls `done` multiple times', () => { let iterator, readDone; before(done => { - iterator = new BufferedIterator({ autoStart: false }); + iterator = new BufferedIterator({ preBuffer: false }); iterator._read = (count, callback) => { readDone = callback; }; scheduleTask(() => { iterator.read(); done(); }); }); @@ -1546,6 +1750,23 @@ describe('BufferedIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -1727,6 +1948,19 @@ describe('BufferedIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -1785,7 +2019,8 @@ describe('BufferedIterator', () => { expect(iterator.read()).to.equal('a'); }); - it('should return null on subsequent reads', () => { + it('should return null on subsequent reads (1)', () => { + expect(iterator.read()).to.be.null; expect(iterator.read()).to.be.null; }); @@ -1909,6 +2144,19 @@ describe('BufferedIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -2032,6 +2280,23 @@ describe('BufferedIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); diff --git a/test/ClonedIterator-test.js b/test/ClonedIterator-test.js index 8b97ba4..3d32c37 100644 --- a/test/ClonedIterator-test.js +++ b/test/ClonedIterator-test.js @@ -87,8 +87,17 @@ describe('ClonedIterator', () => { expect(clone.source).to.be.undefined; }); - it('should not have emitted the `readable` event', () => { - clone._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + clone._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + clone._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + clone.on('end', done); + clone.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -133,8 +142,17 @@ describe('ClonedIterator', () => { getClone().toString().should.equal('[ClonedIterator {source: [EmptyIterator]}]'); }); - it('should not have emitted the `readable` event', () => { - getClone()._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + getClone()._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + getClone()._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + getClone().on('end', done); + getClone().on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -145,11 +163,19 @@ describe('ClonedIterator', () => { getClone().ended.should.be.true; }); + it('should not have been destroyed', () => { + getClone().destroyed.should.be.false; + }); + + it('should be done', () => { + getClone().done.should.be.true; + }); + it('should not be readable', () => { getClone().readable.should.be.false; }); - it('should return null on read', () => { + it('should return null when read is called', () => { expect(getClone().read()).to.be.null; }); }); @@ -226,6 +252,16 @@ describe('ClonedIterator', () => { getClone()._eventCounts.readable.should.equal(1); }); + + it('should not have emitted the `end` event', () => { + getClone()._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + getClone().on('end', done); + getClone().on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { getClone()._eventCounts.end.should.equal(1); }); @@ -234,11 +270,19 @@ describe('ClonedIterator', () => { getClone().ended.should.be.true; }); + it('should not have been destroyed', () => { + getClone().destroyed.should.be.false; + }); + + it('should be done', () => { + getClone().done.should.be.true; + }); + it('should not be readable', () => { getClone().readable.should.be.false; }); - it('should return null on read', () => { + it('should return null when read is called', () => { expect(getClone().read()).to.be.null; }); }); @@ -301,6 +345,16 @@ describe('ClonedIterator', () => { getClone()._eventCounts.readable.should.equal(1); }); + + it('should not have emitted the `end` event', () => { + getClone()._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + getClone().on('end', done); + getClone().on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { getClone()._eventCounts.end.should.equal(1); }); @@ -309,11 +363,19 @@ describe('ClonedIterator', () => { getClone().ended.should.be.true; }); + it('should not have been destroyed', () => { + getClone().destroyed.should.be.false; + }); + + it('should be done', () => { + getClone().done.should.be.true; + }); + it('should not be readable', () => { getClone().readable.should.be.false; }); - it('should return null on read', () => { + it('should return null when read is called', () => { expect(getClone().read()).to.be.null; }); }); @@ -402,6 +464,16 @@ describe('ClonedIterator', () => { getClone()._eventCounts.readable.should.equal(1); }); + + it('should not have emitted the `end` event', () => { + getClone()._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + getClone().on('end', done); + getClone().on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { getClone()._eventCounts.end.should.equal(1); }); @@ -410,11 +482,19 @@ describe('ClonedIterator', () => { getClone().ended.should.be.true; }); + it('should not have been destroyed', () => { + getClone().destroyed.should.be.false; + }); + + it('should be done', () => { + getClone().done.should.be.true; + }); + it('should not be readable', () => { getClone().readable.should.be.false; }); - it('should return null on read', () => { + it('should return null when read is called', () => { expect(getClone().read()).to.be.null; }); }); @@ -763,6 +843,7 @@ describe('ClonedIterator', () => { iterator.removeListener('error', noop); done(); }); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should not leave any error handlers attached', () => { diff --git a/test/EmptyIterator-test.js b/test/EmptyIterator-test.js index caf21ab..064c085 100644 --- a/test/EmptyIterator-test.js +++ b/test/EmptyIterator-test.js @@ -5,6 +5,7 @@ import { } from '../dist/asynciterator.js'; import { EventEmitter } from 'events'; +import { promisifyEventEmitter } from 'event-emitter-promisify'; describe('EmptyIterator', () => { describe('The EmptyIterator function', () => { @@ -54,8 +55,17 @@ describe('EmptyIterator', () => { iterator.toString().should.equal('[EmptyIterator]'); }); - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -74,4 +84,23 @@ describe('EmptyIterator', () => { expect(iterator.read()).to.be.null; }); }); + + describe('An EmptyIterator should not emit until read from', () => { + it('no awaiting', async () => { + const iterator = new EmptyIterator(); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting undefined', async () => { + const iterator = new EmptyIterator(); + await undefined; + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting promise', async () => { + const iterator = new EmptyIterator(); + await Promise.resolve(); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + }); }); diff --git a/test/IntegerIterator-test.js b/test/IntegerIterator-test.js index 961805d..1567631 100644 --- a/test/IntegerIterator-test.js +++ b/test/IntegerIterator-test.js @@ -5,6 +5,7 @@ import { } from '../dist/asynciterator.js'; import { EventEmitter } from 'events'; +import { promisifyEventEmitter } from 'event-emitter-promisify'; describe('IntegerIterator', () => { describe('The IntegerIterator function', () => { @@ -171,6 +172,20 @@ describe('IntegerIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -258,6 +273,19 @@ describe('IntegerIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -289,7 +317,20 @@ describe('IntegerIterator', () => { describe('before reading', () => { it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + iterator._eventCounts.readable.should.equal(1); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -322,8 +363,21 @@ describe('IntegerIterator', () => { }); describe('before reading', () => { - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -357,7 +411,20 @@ describe('IntegerIterator', () => { describe('before reading', () => { it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -390,8 +457,17 @@ describe('IntegerIterator', () => { }); describe('before reading', () => { - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -437,4 +513,42 @@ describe('IntegerIterator', () => { }); }); }); + + describe('An range iterator with no elements should not emit until read from (range constructor)', () => { + it('no awaiting', async () => { + const iterator = range(0, 0); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting undefined', async () => { + const iterator = range(0, 0); + await undefined; + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting promise', async () => { + const iterator = range(0, 0); + await Promise.resolve(); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + }); + + describe('An IntegerIterator with no elements should not emit until read from', () => { + it('no awaiting', async () => { + const iterator = new IntegerIterator({ start: 0, end: 0, step: 1 }); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting undefined', async () => { + const iterator = new IntegerIterator({ start: 0, end: 0, step: 1 }); + await undefined; + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + + it('awaiting promise', async () => { + const iterator = new IntegerIterator({ start: 0, end: 0, step: 1 }); + await Promise.resolve(); + await expect(await promisifyEventEmitter(iterator.on('data', () => { /* */ }))).to.be.undefined; + }); + }); }); diff --git a/test/MultiTransformIterator-test.js b/test/MultiTransformIterator-test.js index fc0c26b..9893d5a 100644 --- a/test/MultiTransformIterator-test.js +++ b/test/MultiTransformIterator-test.js @@ -10,6 +10,7 @@ import { } from '../dist/asynciterator.js'; import { EventEmitter } from 'events'; +import { expect } from 'chai'; describe('MultiTransformIterator', () => { describe('The MultiTransformIterator function', () => { @@ -39,6 +40,26 @@ describe('MultiTransformIterator', () => { }); }); + describe('A MultiTransformIterator without options', () => { + let iterator, source; + before(() => { + source = new ArrayIterator(['a', 'b', 'c', 'd', 'e', 'f']); + iterator = new MultiTransformIterator(source, { preBuffer: false }); + }); + + describe('when reading items', () => { + const items = []; + before(done => { + iterator.on('data', item => { items.push(item); }); + iterator.on('end', done); + }); + + it('should return items as they are', () => { + items.should.deep.equal(['a', 'b', 'c', 'd', 'e', 'f']); + }); + }); + }); + describe('A MultiTransformIterator without options', () => { let iterator, source; before(() => { @@ -59,11 +80,24 @@ describe('MultiTransformIterator', () => { }); }); + describe('A MultiTransformIterator with an ended AsyncIterator Source', () => { + let iterator, source; + before(() => { + source = new AsyncIterator(); + iterator = new MultiTransformIterator(source); + source._end(); + }); + + it('should be closed', () => { + expect(iterator.closed).to.be.true; + }); + }); + describe('A MultiTransformIterator with transformers that emit 0 items', () => { let iterator, source; before(() => { source = new ArrayIterator(['a', 'b', 'c', 'd', 'e', 'f']); - iterator = new MultiTransformIterator(source, { autoStart: false }); + iterator = new MultiTransformIterator(source, { preBuffer: false }); iterator._createTransformer = sinon.spy(() => new EmptyIterator()); }); @@ -139,6 +173,34 @@ describe('MultiTransformIterator', () => { }); }); + describe('A MultiTransformIterator with transformers that synchronously emit 5 items', () => { + let iterator, source; + before(() => { + source = new ArrayIterator(['a', 'b', 'c', 'd', 'e', 'f']); + iterator = new MultiTransformIterator(source); + iterator._createTransformer = sinon.spy(item => new ArrayIterator([`${item}1`, `${item}2`, `${item}3`, `${item}4`, `${item}5`])); + }); + + describe('when reading items', () => { + const items = []; + before(done => { + iterator.on('data', item => { items.push(item); }); + iterator.on('end', done); + }); + + it('should return the transformed items', () => { + items.should.deep.equal([ + 'a1', 'a2', 'a3', 'a4', 'a5', + 'b1', 'b2', 'b3', 'b4', 'b5', + 'c1', 'c2', 'c3', 'c4', 'c5', + 'd1', 'd2', 'd3', 'd4', 'd5', + 'e1', 'e2', 'e3', 'e4', 'e5', + 'f1', 'f2', 'f3', 'f4', 'f5', + ]); + }); + }); + }); + describe('A MultiTransformIterator with transformers that asynchronously close', () => { let iterator, source; before(() => { @@ -293,6 +355,35 @@ describe('MultiTransformIterator', () => { }); }); + + describe('A MultiTransformIterator with optional set to true', () => { + let iterator, source; + before(() => { + source = new ArrayIterator([1, 2, 3, 4, 5, 6]); + iterator = new MultiTransformIterator(source, { optional: true }); + iterator._createTransformer = sinon.spy(item => { + switch (item) { + case 1: return new ArrayIterator(['1', '2', '3', '4']); + case 2: return null; + default: return new SingletonIterator(`t${item}`); + } + }); + }); + + describe('when reading items', () => { + const items = []; + before(done => { + iterator.on('data', item => { items.push(item); }); + iterator.on('end', done); + }); + + it('should return the transformed items, and originals when the transformer is empty', () => { + items.should.deep.equal(['1', '2', '3', '4', 2, 't3', 't4', 't5', 't6']); + }); + }); + }); + + describe('A MultiTransformIterator with transformers that error', () => { let iterator, source; before(() => { diff --git a/test/SimpleTransformIterator-test.js b/test/SimpleTransformIterator-test.js index e033b8e..cb975bd 100644 --- a/test/SimpleTransformIterator-test.js +++ b/test/SimpleTransformIterator-test.js @@ -330,6 +330,19 @@ describe('SimpleTransformIterator', () => { source.read.should.have.been.calledOnce; }); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -556,7 +569,7 @@ describe('SimpleTransformIterator', () => { before(() => { source = new IntegerIterator({ start: 1, end: 10 }); sinon.spy(source, 'read'); - iterator = new SimpleTransformIterator(source, { offset: Infinity, autoStart: false }); + iterator = new SimpleTransformIterator(source, { offset: Infinity, preBuffer: false }); }); describe('when reading items', () => { @@ -631,7 +644,7 @@ describe('SimpleTransformIterator', () => { before(() => { source = new IntegerIterator({ start: 1, end: 10 }); sinon.spy(source, 'read'); - iterator = new SimpleTransformIterator(source, { limit: 0, autoStart: false }); + iterator = new SimpleTransformIterator(source, { limit: 0, preBuffer: false }); }); describe('when reading items', () => { @@ -706,7 +719,7 @@ describe('SimpleTransformIterator', () => { before(() => { source = new IntegerIterator({ start: 1, end: 10 }); sinon.spy(source, 'read'); - iterator = new SimpleTransformIterator(source, { limit: -1, autoStart: false }); + iterator = new SimpleTransformIterator(source, { limit: -1, preBuffer: false }); }); describe('when reading items', () => { @@ -731,7 +744,7 @@ describe('SimpleTransformIterator', () => { before(() => { source = new IntegerIterator({ start: 1, end: 10 }); sinon.spy(source, 'read'); - iterator = new SimpleTransformIterator(source, { limit: -Infinity, autoStart: false }); + iterator = new SimpleTransformIterator(source, { limit: -Infinity, preBuffer: false }); }); describe('when reading items', () => { diff --git a/test/SingletonIterator-test.js b/test/SingletonIterator-test.js index 177ab28..3116f00 100644 --- a/test/SingletonIterator-test.js +++ b/test/SingletonIterator-test.js @@ -54,16 +54,65 @@ describe('SingletonIterator', () => { iterator.toString().should.equal('[SingletonIterator]'); }); - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); - it('should have ended', () => { - iterator.ended.should.be.true; + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + }); + + describe('An SingletonIterator without item', () => { + let iterator; + before(() => { + iterator = new SingletonIterator(null); + captureEvents(iterator, 'readable', 'end'); + }); + + it('should provide a readable `toString` representation', () => { + iterator.toString().should.equal('[SingletonIterator]'); + }); + + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + + it('should have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(1); }); it('should not be readable', () => { @@ -132,4 +181,64 @@ describe('SingletonIterator', () => { }); }); }); + + + describe('An SingletonIterator with an item', () => { + let iterator, item; + before(() => { + iterator = new SingletonIterator(1); + captureEvents(iterator, 'readable', 'end'); + }); + + describe('before calling read', () => { + it('should provide a readable `toString` representation', () => { + iterator.toString().should.equal('[SingletonIterator (1)]'); + }); + + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('should be readable', () => { + iterator.readable.should.be.true; + }); + }); + + describe('after calling read for the first time', () => { + before(() => { item = iterator.read(); }); + + it('should provide a readable `toString` representation', () => { + iterator.toString().should.equal('[SingletonIterator]'); + }); + + it('should read the first item of the array', () => { + item.should.equal(1); + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(1); + }); + + it('should have ended', () => { + iterator.ended.should.be.true; + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + }); + }); }); diff --git a/test/TransformIterator-test.js b/test/TransformIterator-test.js index e5d8116..030d958 100644 --- a/test/TransformIterator-test.js +++ b/test/TransformIterator-test.js @@ -155,10 +155,28 @@ describe('TransformIterator', () => { expect(iterator.source).to.be.undefined; }); - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); + it('should have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(1); + }); + + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -183,16 +201,78 @@ describe('TransformIterator', () => { iterator = new TransformIterator(source = new EmptyIterator()); captureEvents(iterator, 'readable', 'end'); expect(source._events).to.not.contain.key('data'); - expect(source._events).to.not.contain.key('readable'); - expect(source._events).to.not.contain.key('end'); + // expect(source._events).to.not.contain.key('end'); + }); + + it('should expose the source in the `source` property', () => { + iterator.source.should.equal(source); + }); + + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(1); + }); + + it('should have ended', () => { + iterator.ended.should.be.true; + }); + + it('should not be readable', () => { + iterator.readable.should.be.false; + }); + + it('should return null when read is called', () => { + expect(iterator.read()).to.be.null; + }); + }); + + + describe('A TransformIterator initialized with a completed empty source', () => { + let iterator, source; + before(() => { + source = new EmptyIterator(); + source.read(); + iterator = new TransformIterator(source); + captureEvents(iterator, 'readable', 'end'); + expect(source._events).to.not.contain.key('data'); + // expect(source._events).to.not.contain.key('end'); }); it('should expose the source in the `source` property', () => { iterator.source.should.equal(source); }); - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { @@ -268,14 +348,29 @@ describe('TransformIterator', () => { describe('after the source ends', () => { before(() => { source.close(); }); - it('should not have emitted the `readable` event', () => { - iterator._eventCounts.readable.should.equal(0); + + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); + it('should have ended', () => { iterator.ended.should.be.true; }); @@ -290,6 +385,26 @@ describe('TransformIterator', () => { }); }); + describe('A TransformIterator without options', () => { + let iterator, source; + before(() => { + source = new ArrayIterator(['a', 'b', 'c', 'd', 'e', 'f']); + iterator = new TransformIterator(source, { preBuffer: false }); + }); + + describe('when reading items', () => { + const items = []; + before(done => { + iterator.on('data', item => { items.push(item); }); + iterator.on('end', done); + }); + + it('should return items as they are', () => { + items.should.deep.equal(['a', 'b', 'c', 'd', 'e', 'f']); + }); + }); + }); + describe('A TransformIterator with a one-item source', () => { let iterator, source; before(() => { @@ -301,8 +416,8 @@ describe('TransformIterator', () => { }); describe('before reading an item', () => { - it('should have called `read` on the source', () => { - source.read.should.have.been.calledOnce; + it('should have called `read` on the source once to read, once to end', () => { + source.read.should.have.been.calledTwice; }); it('should have emitted the `readable` event', () => { @@ -334,6 +449,19 @@ describe('TransformIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -441,6 +569,19 @@ describe('TransformIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -550,8 +691,17 @@ describe('TransformIterator', () => { iterator._eventCounts.readable.should.equal(1); }); - it('should have emitted the `end` event', () => { - iterator._eventCounts.end.should.equal(1); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have ended', () => { @@ -651,8 +801,8 @@ describe('TransformIterator', () => { describe('after the promise resolves', () => { before(() => resolvePromise(source)); - it('should have called `read` on the source', () => { - source.read.should.have.been.calledOnce; + it('should have called `read` on the source once to read, once to end', () => { + source.read.should.have.been.calledTwice; }); it('should have emitted the `readable` event', () => { @@ -684,8 +834,17 @@ describe('TransformIterator', () => { iterator._eventCounts.readable.should.equal(1); }); - it('should have emitted the `end` event', () => { - iterator._eventCounts.end.should.equal(1); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have ended', () => { @@ -702,7 +861,7 @@ describe('TransformIterator', () => { }); }); - describe('A TransformIterator with a promise and without autoStart', () => { + describe('A TransformIterator with a promise and without preBuffer', () => { let iterator, source, sourcePromise, resolvePromise; before(() => { source = new ArrayIterator(['a']); @@ -711,7 +870,7 @@ describe('TransformIterator', () => { }); sinon.spy(source, 'read'); sinon.spy(sourcePromise, 'then'); - iterator = new TransformIterator(sourcePromise, { autoStart: false }); + iterator = new TransformIterator(sourcePromise, { preBuffer: false }); captureEvents(iterator, 'readable', 'end'); }); @@ -774,8 +933,8 @@ describe('TransformIterator', () => { describe('after the promise resolves', () => { before(() => resolvePromise(source)); - it('should have called `read` on the source', () => { - source.read.should.have.been.calledOnce; + it('should have called `read` on the source once to read, once to end', () => { + source.read.should.have.been.calledTwice; }); it('should have emitted another `readable` event', () => { @@ -807,10 +966,20 @@ describe('TransformIterator', () => { iterator._eventCounts.readable.should.equal(2); }); - it('should have emitted the `end` event', () => { - iterator._eventCounts.end.should.equal(1); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); }); + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should have ended', () => { iterator.ended.should.be.true; }); @@ -888,8 +1057,8 @@ describe('TransformIterator', () => { createSource.should.have.been.calledOnce; }); - it('should have called `read` on the source', () => { - source.read.should.have.been.calledOnce; + it('should have called `read` on the source once to read, once to end', () => { + source.read.should.have.been.calledTwice; }); it('should have emitted the `readable` event', () => { @@ -926,6 +1095,19 @@ describe('TransformIterator', () => { iterator._eventCounts.readable.should.equal(1); }); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + it('should have emitted the `end` event', () => { iterator._eventCounts.end.should.equal(1); }); @@ -987,8 +1169,8 @@ describe('TransformIterator', () => { describe('after the promise resolves', () => { before(() => resolvePromise(source)); - it('should have called `read` on the source', () => { - source.read.should.have.been.calledOnce; + it('should have called `read` on the source once to read, once to end', () => { + source.read.should.have.been.calledTwice; }); it('should have emitted the `readable` event', () => { @@ -1020,10 +1202,20 @@ describe('TransformIterator', () => { iterator._eventCounts.readable.should.equal(1); }); - it('should have emitted the `end` event', () => { - iterator._eventCounts.end.should.equal(1); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); }); + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should have ended', () => { iterator.ended.should.be.true; }); @@ -1038,7 +1230,7 @@ describe('TransformIterator', () => { }); }); - describe('A TransformIterator with a source creation function and without autoStart', () => { + describe('A TransformIterator with a source creation function and without preBuffer', () => { let iterator, source, createSource, sourcePromise, resolvePromise; before(() => { source = new ArrayIterator(['a']); @@ -1047,7 +1239,7 @@ describe('TransformIterator', () => { resolvePromise = resolve; }); createSource = sinon.spy(() => sourcePromise); - iterator = new TransformIterator({ autoStart: false, source: createSource }); + iterator = new TransformIterator({ preBuffer: false, source: createSource }); captureEvents(iterator, 'readable', 'end'); }); @@ -1110,8 +1302,8 @@ describe('TransformIterator', () => { describe('after the promise resolves', () => { before(() => resolvePromise(source)); - it('should have called `read` on the source', () => { - source.read.should.have.been.calledOnce; + it('should have called `read` on the source once to read, once to end', () => { + source.read.should.have.been.calledTwice; }); it('should have emitted another `readable` event', () => { @@ -1143,10 +1335,20 @@ describe('TransformIterator', () => { iterator._eventCounts.readable.should.equal(2); }); - it('should have emitted the `end` event', () => { - iterator._eventCounts.end.should.equal(1); + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); }); + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should have ended', () => { iterator.ended.should.be.true; }); @@ -1165,7 +1367,7 @@ describe('TransformIterator', () => { let iterator, source; before(() => { source = new ArrayIterator([1, 2, 3]); - iterator = new TransformIterator(source, { autoStart: false }); + iterator = new TransformIterator(source, { preBuffer: false }); }); describe('after being closed', () => { @@ -1173,6 +1375,7 @@ describe('TransformIterator', () => { iterator.read(); iterator.close(); iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should have destroyed the source', () => { @@ -1185,7 +1388,7 @@ describe('TransformIterator', () => { let iterator, source; before(() => { source = new ArrayIterator([1, 2, 3]); - iterator = new TransformIterator(source, { autoStart: false, destroySource: false }); + iterator = new TransformIterator(source, { preBuffer: false, destroySource: false }); }); describe('after being closed', () => { @@ -1193,6 +1396,7 @@ describe('TransformIterator', () => { iterator.read(); iterator.close(); iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should not have destroyed the source', () => { @@ -1252,6 +1456,7 @@ describe('TransformIterator', () => { source.removeListener('error', noop); done(); }); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should not re-emit the error', () => { @@ -1405,8 +1610,21 @@ describe('TransformIterator', () => { source.read.should.have.been.calledOnce; }); - it('should have emitted the `end` event', () => { - iterator._eventCounts.end.should.equal(1); + it('should have emitted the `readable` event', () => { + iterator._eventCounts.readable.should.equal(1); + }); + + it('should not have emitted the `end` event', () => { + iterator._eventCounts.end.should.equal(0); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); }); it('should not be readable', () => { @@ -1505,7 +1723,7 @@ describe('TransformIterator', () => { }); }); - describe('Two transformers in sequence with autostart', () => { + describe('Two transformers in sequence with preBuffer', () => { let source, transform1, transform2, callback; before(() => { source = new ArrayIterator([]); @@ -1513,6 +1731,7 @@ describe('TransformIterator', () => { transform2 = new TransformIterator(transform1); callback = sinon.spy(); transform2.on('end', callback); + transform2.on('data', () => { throw new Error('should not emit data'); }); }); describe('before attaching a data listener', () => { @@ -1522,12 +1741,12 @@ describe('TransformIterator', () => { }); }); - describe('Two transformers in sequence without autostart', () => { + describe('Two transformers in sequence without preBuffer', () => { let source, transform1, transform2, callback; before(() => { source = new ArrayIterator([]); - transform1 = new TransformIterator(source, { autoStart: false }); - transform2 = new TransformIterator(transform1, { autoStart: false }); + transform1 = new TransformIterator(source, { preBuffer: false }); + transform2 = new TransformIterator(transform1, { preBuffer: false }); callback = sinon.spy(); transform2.on('end', callback); }); @@ -1539,8 +1758,9 @@ describe('TransformIterator', () => { }); describe('after attaching a data listener', () => { - before(() => { + before(done => { transform2.on('data', sinon.spy()); + transform2.on('end', done); }); it('should have emitted the end event', () => { diff --git a/test/UnionIterator-test.js b/test/UnionIterator-test.js index 308f52d..d8e8214 100644 --- a/test/UnionIterator-test.js +++ b/test/UnionIterator-test.js @@ -10,6 +10,7 @@ import { } from '../dist/asynciterator.js'; import { EventEmitter } from 'events'; +import { expect } from 'chai'; describe('UnionIterator', () => { describe('The UnionIterator function', () => { @@ -68,6 +69,31 @@ describe('UnionIterator', () => { (await toArray(iterator)).sort().should.eql([0, 1, 2]); }); + it('should include all data from 1 non-empty and 3 normal empty sources and one empty source that is done', async () => { + const empty = new EmptyIterator(); + empty.read(); + empty.read(); + expect(empty.done).to.be.true; + + const iterator = new UnionIterator([ + empty, + new EmptyIterator(), + range(0, 2), + new EmptyIterator(), + new EmptyIterator(), + ]); + (await toArray(iterator)).sort().should.eql([0, 1, 2]); + }); + + it('taking union over completed empty iterator', async () => { + const empty = new EmptyIterator(); + empty.read(); + expect(empty.done).to.be.true; + + const iterator = new UnionIterator(empty); + (await toArray(iterator)).sort().should.eql([]); + }); + describe('when constructed with an array of 0 sources', () => { let iterator; before(() => { @@ -75,16 +101,24 @@ describe('UnionIterator', () => { iterator = new UnionIterator(sources); }); + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('should read null', () => { + expect(iterator.read()).to.be.null; + }); + it('should have ended', () => { iterator.ended.should.be.true; }); }); - describe('when constructed with an array of 0 sources without autoStart', () => { + describe('when constructed with an array of 0 sources without preBuffer', () => { let iterator; before(() => { const sources = []; - iterator = new UnionIterator(sources, { autoStart: false }); + iterator = new UnionIterator(sources, { preBuffer: false }); }); describe('before reading', () => { @@ -99,6 +133,14 @@ describe('UnionIterator', () => { scheduleTask(done); }); + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('should read null', () => { + expect(iterator.read()).to.be.null; + }); + it('should have ended', () => { iterator.ended.should.be.true; }); @@ -135,6 +177,14 @@ describe('UnionIterator', () => { iterator = new UnionIterator(new EmptyIterator()); }); + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('should read null', () => { + expect(iterator.read()).to.be.null; + }); + it('should have ended', () => { iterator.ended.should.be.true; }); @@ -147,16 +197,45 @@ describe('UnionIterator', () => { iterator = new UnionIterator(new ArrayIterator(sources)); }); + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('should read null', () => { + expect(iterator.read()).to.be.null; + }); + it('should have ended', () => { iterator.ended.should.be.true; }); }); - describe('when constructed with an iterator of 0 sources without autoStart', () => { + describe('when constructed with an iterator of 0 sources', () => { let iterator; before(() => { const sources = []; - iterator = new UnionIterator(new ArrayIterator(sources), { autoStart: false }); + iterator = new UnionIterator(new ArrayIterator(sources), { preBuffer: false }); + }); + + it('should not have ended', () => { + iterator.ended.should.be.false; + }); + + it('emit end once data is subscribed', done => { + iterator.on('end', done); + iterator.on('data', () => { throw new Error('should not emit data'); }); + }); + + it('should have ended', () => { + iterator.ended.should.be.true; + }); + }); + + describe('when constructed with an iterator of 0 sources without preBuffer', () => { + let iterator; + before(() => { + const sources = []; + iterator = new UnionIterator(new ArrayIterator(sources), { preBuffer: false }); }); describe('before reading', () => { @@ -171,6 +250,10 @@ describe('UnionIterator', () => { scheduleTask(done); }); + it('should read null', () => { + expect(iterator.read()).to.be.null; + }); + it('should have ended', () => { iterator.ended.should.be.true; }); @@ -216,13 +299,13 @@ describe('UnionIterator', () => { }); }); - describe('when constructed with an iterator and with autoStart', () => { + describe('when constructed with an iterator and with preBuffer', () => { let iterator, sourceIterator; before(() => { const sources = [range(0, 2), range(3, 6)]; sourceIterator = new ArrayIterator(sources); sinon.spy(sourceIterator, 'read'); - iterator = new UnionIterator(sourceIterator, { autoStart: true }); + iterator = new UnionIterator(sourceIterator, { preBuffer: true }); }); describe('before reading', () => { @@ -260,13 +343,13 @@ describe('UnionIterator', () => { }); }); - describe('when constructed with an iterator and without autoStart', () => { + describe('when constructed with an iterator and without preBuffer', () => { let iterator, sourceIterator; before(() => { const sources = [range(0, 2), range(3, 6)]; sourceIterator = new ArrayIterator(sources); sinon.spy(sourceIterator, 'read'); - iterator = new UnionIterator(sourceIterator, { autoStart: false }); + iterator = new UnionIterator(sourceIterator, { preBuffer: false }); }); describe('before reading', () => { diff --git a/test/integration-test.js b/test/integration-test.js index ba45ea5..d882c6d 100644 --- a/test/integration-test.js +++ b/test/integration-test.js @@ -6,13 +6,13 @@ import { } from '../dist/asynciterator.js'; describe('Integration tests', () => { - describe('A sequence of ArrayIterator, TransformIterator, and Unioniterator without autoStart', () => { + describe('A sequence of ArrayIterator, TransformIterator, and Unioniterator without preBuffer', () => { let arrayIterator, transformIterator, unionIterator; before(() => { - arrayIterator = new ArrayIterator([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { autoStart: false }); - transformIterator = new TransformIterator(arrayIterator, { autoStart: false }); - unionIterator = new UnionIterator([transformIterator], { autoStart: false }); + arrayIterator = new ArrayIterator([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], { preBuffer: false }); + transformIterator = new TransformIterator(arrayIterator, { preBuffer: false }); + unionIterator = new UnionIterator([transformIterator], { preBuffer: false }); }); it('emits a data event', done => { @@ -26,11 +26,11 @@ describe('Integration tests', () => { }); describe('Cloning iterators', () => { - describe('A clone of an empty ArrayIterator without autoStart', () => { + describe('A clone of an empty ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator; before(() => { - arrayIterator = new ArrayIterator([], { autoStart: false }); + arrayIterator = new ArrayIterator([], { preBuffer: false }); clonedIterator = arrayIterator.clone(); }); @@ -40,11 +40,11 @@ describe('Integration tests', () => { }); }); - describe('An async clone of an empty ArrayIterator without autoStart', () => { + describe('An async clone of an empty ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator; before(async () => { - arrayIterator = new ArrayIterator([], { autoStart: false }); + arrayIterator = new ArrayIterator([], { preBuffer: false }); await new Promise(scheduleTask); clonedIterator = arrayIterator.clone(); @@ -56,11 +56,11 @@ describe('Integration tests', () => { }); }); - describe('A multi-clone of an empty ArrayIterator without autoStart', () => { + describe('A multi-clone of an empty ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator1, clonedIterator2; before(() => { - arrayIterator = new ArrayIterator([], { autoStart: false }); + arrayIterator = new ArrayIterator([], { preBuffer: false }); clonedIterator1 = arrayIterator.clone(); clonedIterator2 = arrayIterator.clone(); }); @@ -76,11 +76,11 @@ describe('Integration tests', () => { }); }); - describe('An async multi-clone of an empty ArrayIterator without autoStart', () => { + describe('An async multi-clone of an empty ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator1, clonedIterator2; before(async () => { - arrayIterator = new ArrayIterator([], { autoStart: false }); + arrayIterator = new ArrayIterator([], { preBuffer: false }); await new Promise(resolve => scheduleTask(resolve)); clonedIterator1 = arrayIterator.clone(); @@ -98,11 +98,11 @@ describe('Integration tests', () => { }); }); - describe('A double clone of an empty ArrayIterator without autoStart', () => { + describe('A double clone of an empty ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator; before(() => { - arrayIterator = new ArrayIterator([], { autoStart: false }); + arrayIterator = new ArrayIterator([], { preBuffer: false }); clonedIterator = arrayIterator.clone().clone(); }); @@ -112,12 +112,12 @@ describe('Integration tests', () => { }); }); - describe('A clone of a sequence of an empty ArrayIterator, and TransformIterator without autoStart', () => { + describe('A clone of a sequence of an empty ArrayIterator, and TransformIterator without preBuffer', () => { let arrayIterator, transformIterator, clonedIterator; before(() => { - arrayIterator = new ArrayIterator([], { autoStart: false }); - transformIterator = new TransformIterator(arrayIterator, { autoStart: false }); + arrayIterator = new ArrayIterator([], { preBuffer: false }); + transformIterator = new TransformIterator(arrayIterator, { preBuffer: false }); clonedIterator = transformIterator.clone(); }); @@ -127,13 +127,13 @@ describe('Integration tests', () => { }); }); - describe('A clone of a sequence of an empty ArrayIterator, TransformIterator, and Unioniterator without autoStart', () => { + describe('A clone of a sequence of an empty ArrayIterator, TransformIterator, and Unioniterator without preBuffer', () => { let arrayIterator, transformIterator, unionIterator, clonedIterator; before(() => { - arrayIterator = new ArrayIterator([], { autoStart: false }); - transformIterator = new TransformIterator(arrayIterator, { autoStart: false }); - unionIterator = new UnionIterator([transformIterator], { autoStart: false }); + arrayIterator = new ArrayIterator([], { preBuffer: false }); + transformIterator = new TransformIterator(arrayIterator, { preBuffer: false }); + unionIterator = new UnionIterator([transformIterator], { preBuffer: false }); clonedIterator = unionIterator.clone(); }); @@ -143,11 +143,11 @@ describe('Integration tests', () => { }); }); - describe('A clone of an ArrayIterator without autoStart', () => { + describe('A clone of an ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator; before(() => { - arrayIterator = new ArrayIterator([1, 2, 3], { autoStart: false }); + arrayIterator = new ArrayIterator([1, 2, 3], { preBuffer: false }); clonedIterator = arrayIterator.clone(); }); @@ -157,11 +157,11 @@ describe('Integration tests', () => { }); }); - describe('An async clone of an ArrayIterator without autoStart', () => { + describe('An async clone of an ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator; before(async () => { - arrayIterator = new ArrayIterator([1, 2, 3], { autoStart: false }); + arrayIterator = new ArrayIterator([1, 2, 3], { preBuffer: false }); await new Promise(scheduleTask); clonedIterator = arrayIterator.clone(); @@ -173,11 +173,11 @@ describe('Integration tests', () => { }); }); - describe('A double clone of an ArrayIterator without autoStart', () => { + describe('A double clone of an ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator; before(() => { - arrayIterator = new ArrayIterator([1, 2, 3], { autoStart: false }); + arrayIterator = new ArrayIterator([1, 2, 3], { preBuffer: false }); clonedIterator = arrayIterator.clone().clone(); }); @@ -187,11 +187,11 @@ describe('Integration tests', () => { }); }); - describe('A double async clone of an ArrayIterator without autoStart', () => { + describe('A double async clone of an ArrayIterator without preBuffer', () => { let arrayIterator, clonedIterator; before(async () => { - arrayIterator = new ArrayIterator([1, 2, 3], { autoStart: false }); + arrayIterator = new ArrayIterator([1, 2, 3], { preBuffer: false }); clonedIterator = arrayIterator.clone(); await new Promise(scheduleTask);