diff --git a/src/common/queue.ts b/src/common/queue.ts index 6c4f33ff..1e5982ca 100644 --- a/src/common/queue.ts +++ b/src/common/queue.ts @@ -1,14 +1,26 @@ +import { pushTo } from './common'; + /** * @module common - */ /** for typedoc */ + */ +/** for typedoc */ export class Queue { + private _evictListeners: ((item: T) => void)[] = []; + public onEvict = pushTo(this._evictListeners); + constructor(private _items: T[] = [], private _limit: number = null) { } enqueue(item: T) { const items = this._items; items.push(item); - if (this._limit && items.length > this._limit) items.shift(); + if (this._limit && items.length > this._limit) this.evict(); + return item; + } + + evict(): T { + const item: T = this._items.shift(); + this._evictListeners.forEach(fn => fn(item)); return item; } diff --git a/test/commonSpec.ts b/test/commonSpec.ts index 9529acbc..7857ba3c 100644 --- a/test/commonSpec.ts +++ b/test/commonSpec.ts @@ -2,6 +2,7 @@ import { defaults, filter, is, eq, not, pattern, val, isInjectable, } from '../src/index'; import { map, mapObj, pick } from '../src/common/common'; +import { Queue } from '../src/common'; describe('common', function() { describe('filter', function() { @@ -162,4 +163,107 @@ describe('common', function() { expect(dest).toEqual({ foo: 2, bar: 4, baz: 6 }); }); }); + + describe('Queue', () => { + it('peekTail() should show the last enqueued item', () => { + const q = new Queue(); + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + expect(q.size()).toBe(3); + expect(q.peekTail()).toBe(3); + }); + + it('peekHead() should show the first enqueued item', () => { + const q = new Queue(); + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + expect(q.size()).toBe(3); + expect(q.peekHead()).toBe(1); + }); + + it('should support a limit (max number of items)', () => { + const q = new Queue([], 2); + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + expect(q.size()).toBe(2); + expect(q.peekHead()).toBe(2); + expect(q.peekTail()).toBe(3); + }); + + it('clear() should remove all items', () => { + const q = new Queue([], 2); + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + expect(q.size()).toBe(2); + + q.clear(); + expect(q.size()).toBe(0); + }); + + it('enqueue() should evict from the head when max length is reached', () => { + const q = new Queue([], 3); + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + expect(q.size()).toBe(3); + + q.enqueue(4); + expect(q.size()).toBe(3); + + const a = q.dequeue(); + const b = q.dequeue(); + const c = q.dequeue(); + + expect(q.size()).toBe(0); + expect([a, b, c]).toEqual([2, 3, 4]); + }); + + it('onEvict() handlers should be called when an item is evicted', () => { + const log = []; + const q = new Queue([], 2); + + q.onEvict(item => log.push(item)); + + q.enqueue(1); + expect(q.size()).toBe(1); + expect(log).toEqual([]); + + q.enqueue(2); + expect(q.size()).toBe(2); + expect(log).toEqual([]); + + q.enqueue(3); + expect(q.size()).toBe(2); + expect(log).toEqual([1]); + + q.enqueue(4); + expect(q.size()).toBe(2); + expect(log).toEqual([1, 2]); + }); + + it('onEvict() should support multiple handlers', () => { + const log = []; + const log2 = []; + const q = new Queue([], 2); + + q.onEvict(item => log.push(item)); + q.onEvict(item => log2.push(item)); + + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + expect(q.size()).toBe(2); + expect(log).toEqual([1]); + expect(log2).toEqual([1]); + + q.enqueue(4); + expect(q.size()).toBe(2); + expect(log).toEqual([1, 2]); + expect(log2).toEqual([1, 2]); + }); + }); });