Skip to content

Instantly share code, notes, and snippets.

@fatso83
Last active March 26, 2018 16:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save fatso83/3773d4cb5f39128b3732 to your computer and use it in GitHub Desktop.
Save fatso83/3773d4cb5f39128b3732 to your computer and use it in GitHub Desktop.
var expect = require('chai').expect;
var sinon = require('sinon');
var base = require('./Collection');
var Collection = base.Collection;
var extended = require('./ExtendedCollection');
var ExtendedCollection = extended.ExtendedCollection;
describe('constructor', function () {
it('should be instantiated with its arguments', function () {
var c = new Collection(1, 2, 3, 4);
expect(c.length).to.equal(4);
});
it('should add the methods to the collection as unlistable properties', function () {
var c = new Collection();
expect(c).to.be.empty;
})
});
describe('length', function () {
it('should not be enumerable', function() {
expect(Object.keys(new Collection)).to.be.empty;
});
it('should be updated as we add items', function () {
var c = new Collection(1, 2);
c.add(3);
expect(c.length).to.equal(3);
});
});
describe('add', function () {
var c1;
beforeEach(function () {
c1 = new Collection();
});
it('should add only the first argument', function () {
c1.add(1, 2, 3);
expect(c1.length).to.equal(1);
});
it('should add all the elements of an array', function () {
c1.add([1, 2, 3]);
expect(c1.length).to.equal(3);
});
});
describe('remove', function () {
it('should remove an identifable element', function () {
function identifiable (id) {
return {
getId : function () { return id; }
}
}
var elemToRemove = identifiable('foobar');
var c = new Collection(
identifiable('a'), identifiable(1010), elemToRemove
);
c.remove(elemToRemove);
expect(c.find('foobar')).to.eql(null);
});
it('should remove an element identfied by a function', function () {
var c = new Collection(1, 2, 3, 4, 5);
c.remove(function (e) { return e === 4});
expect(c).not.to.contain(4);
})
});
describe('subclassing', function () {
it('should be subclassable', function () {
var c = new ExtendedCollection(6,4,2,0,-2);
expect(c.length).to.equal(5);
expect(c.mySquare()).to.equal(5*5);
})
})
/*
* Utility "class" extending Array with lookup functions
*
* Can do everything an Array can, including indexing by brackets,
* use in loop constructions (for, while), etc
*
* Example:
* var foo = new Foo({id : 1})
* foo.getId() === 1; // => true
* var c = new Collection();
* c.add(foo)
* foo === c.find(1); // => true
*
* Typescript conversion of Ben Nadel's Collection class.
* https://gist.github.com/fatso83/3773d4cb5f39128b3732
*
* @author Carl-Erik Kopseng
* @author Ben Nadel (javascript original)
*/
export interface Identifiable {
getId : () => any;
}
export class Collection<T extends Identifiable> implements Array<T> {
constructor(...initialItems:any[]) {
var collection = Object.create(Array.prototype);
Collection.init(collection, initialItems, Collection.prototype);
return collection;
}
static init(collection, initialItems:any[], prototype) {
Object.getOwnPropertyNames(prototype)
.forEach((prop) => {
if (prop === 'constructor') return;
Object.defineProperty(collection, prop, { value: prototype[prop] })
});
// If we don't redefine the property, the length property is suddenly enumerable!
// Failing to do this, this would fail: Object.keys([]) === Object.keys(new Collection() )
Object.defineProperty(collection, 'length', {
value: collection.length,
writable: true,
enumerable: false
});
var itemsToPush = initialItems;
if (Array.isArray(initialItems[0]) && initialItems.length === 1) {
itemsToPush = initialItems[0];
}
Array.prototype.push.apply(collection, itemsToPush);
return collection;
}
// Find an element by checking each element's getId() method
public find(id:any):T;
// Find an element using a lookup function that
// returns true when given the right element
public find(lookupFn:(e:T) => boolean):T ;
find(x:any) {
var res, comparitor;
if (typeof x === 'function') {
comparitor = x;
} else {
comparitor = (e) => {
return e.getId() === x;
}
}
res = [].filter.call(this, comparitor);
if (res.length) return res[0];
else return null;
}
// Add an element
add(value:T);
// Adds all ements in the array (flattens it)
add(arr:T[]);
add(arr:Collection<T>);
add(value) {
// Check to see if the item is an array or a subtype thereof
if (value instanceof Array) {
// Add each sub-item using default push() method.
Array.prototype.push.apply(this, value);
} else {
// Use the default push() method.
Array.prototype.push.call(this, value);
}
// Return this object reference for method chaining.
return this;
}
remove(elem:T):boolean;
remove(lookupFn:(e:T) => boolean):boolean ;
remove(x:any):boolean {
return !!this._remove(x);
}
/**
* @return the removed element if found, else null
*/
_remove(x:any):T {
var arr = this;
var index = -1;
if (typeof x === 'function') {
for (var i = 0, len = arr.length; i < len; i++) {
if (x(this[i])) {
index = i;
break;
}
}
} else {
index = arr.indexOf(x);
}
if (index === -1) {
return null;
}
else {
var res = arr.splice(index, 1);
return res.length ? res[0] : null;
}
}
// dummy declarations
// copied from lib.d.ts in the typescript npm module
toString:()=> string;
toLocaleString:()=> string;
concat:<U extends T[]>(...items:U[])=> T[];
join:(separator?:string)=> string;
pop:()=> T;
push:(...items:T[])=> number;
reverse:()=> T[];
shift:()=> T;
slice:(start?:number, end?:number)=> T[];
sort:(compareFn?:(a:T, b:T) => number)=> T[];
splice:(start?:number, deleteCount?:number, ...items:T[])=> T[];
unshift:(...items:T[])=> number;
indexOf:(searchElement:T, fromIndex?:number)=> number;
lastIndexOf:(searchElement:T, fromIndex?:number)=> number;
every:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> boolean;
some:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> boolean;
forEach:(callbackfn:(value:T, index:number, array:T[]) => void, thisArg?:any)=> void;
map:<U>(callbackfn:(value:T, index:number, array:T[]) => U, thisArg?:any)=> U[];
filter:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> T[];
reduce:<U>(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U;
reduceRight:<U>(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U;
length:number;
[n: number]: T;
}
import coll = require('./Collection');
export class ExtendedCollection<T extends coll.Identifiable> extends coll.Collection<T> {
constructor(...items:T[]) {
var s = <any>super();
coll.Collection.init(s,items,ExtendedCollection.prototype);
return s;
}
mySquare() : number {
return this.length*this.length;
}
}
{
"name": "Collection.ts",
"description": "Typescript conversion of Ben Nadel's original Collection class (http://www.bennadel.com/blog/2292-extending-javascript-arrays-while-keeping-native-bracket-notation-functionality.htm)",
"version": "0.0.2",
"devDependencies": {
"mocha": "^1.21.4",
"chai": "^1.9.1",
"sinon": "^1.10.3"
},
"scripts" : {
"test" : "mocha collection-test.js"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment