From fd953dece113d41dd30fcd6d25877b4c58813350 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Sun, 3 Jan 2016 14:02:38 +0100 Subject: [PATCH] feat(facade/lang): add ES6 Array ponyfills --- ng-metadata.test.ts | 1 + src/facade/lang.ts | 62 ++++++++++++++- test/facade/lang.spec.ts | 167 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 test/facade/lang.spec.ts diff --git a/ng-metadata.test.ts b/ng-metadata.test.ts index 92ff916..db3e090 100644 --- a/ng-metadata.test.ts +++ b/ng-metadata.test.ts @@ -13,6 +13,7 @@ import './test/di/decorators.spec'; import './test/di/povider.spec'; import './test/util/decorators.spec'; import './test/reflection/reflection.spec'; +import './test/facade/lang.spec'; describe( 'ng-metadata', ()=> { diff --git a/src/facade/lang.ts b/src/facade/lang.ts index 13e8ac3..631bd6e 100644 --- a/src/facade/lang.ts +++ b/src/facade/lang.ts @@ -149,10 +149,17 @@ function _getFuncName( func: Function ): string { const parsedFnStatement = /function\s*([^\s(]+)/.exec(stringify(func)); const [,name=''] = parsedFnStatement || []; + // if Function.name doesn't exist exec will find match otherwise return name property return name || stringify(func); } +/** + * controller instance of directive is exposed on jqLiteElement.data() + * under the name: `$` + Ctor + `Controller` + * @param name + * @returns {string} + */ export function controllerKey( name: string ): string { return '$' + name + 'Controller'; } @@ -160,9 +167,60 @@ export function hasCtorInjectables( Type ): boolean { return (Array.isArray( Type.$inject ) && Type.$inject.length !== 0); } export function firstToLowerCase( value: string ): string { - return value.charAt( 0 ).toLocaleLowerCase() + value.substring( 1 ); + return _firstTo(value,String.prototype.toLowerCase); } export function firstToUpperCase( value: string ): string { - return value.charAt( 0 ).toUpperCase() + value.substring( 1 ); + return _firstTo(value,String.prototype.toUpperCase); +} +function _firstTo( value: string, cb: Function ): string { + return cb.call( value.charAt( 0 ) ) + value.substring( 1 ); } + +export function find(arr, predicate, ctx?) { + if ( isFunction( Array.prototype[ 'find' ] ) ) { + return arr.find( predicate, ctx ); + } + + ctx = ctx || this; + var length = arr.length; + var i; + + if (!isFunction(predicate)) { + throw new TypeError(`${predicate} is not a function`); + } + + for (i = 0; i < length; i++) { + if (predicate.call(ctx, arr[i], i, arr)) { + return arr[i]; + } + } + + return undefined; + +} + +export function findIndex(arr, predicate, ctx?) { + if (isFunction(Array.prototype['findIndex'])) { + return arr.findIndex(predicate, ctx); + } + + if (!isFunction(predicate)) { + throw new TypeError('predicate must be a function'); + } + + var list = Object(arr); + var len = list.length; + + if (len === 0) { + return -1; + } + + for (var i = 0; i < len; i++) { + if (predicate.call(ctx, list[i], i, list)) { + return i; + } + } + + return -1; +} diff --git a/test/facade/lang.spec.ts b/test/facade/lang.spec.ts new file mode 100644 index 0000000..656f135 --- /dev/null +++ b/test/facade/lang.spec.ts @@ -0,0 +1,167 @@ +import {expect} from 'chai'; +import { + resolveDirectiveNameFromSelector, + assign, + stringify, + hasCtorInjectables, + firstToLowerCase, + firstToUpperCase, + find, + findIndex +} from '../../src/facade/lang'; + +describe.only( `facade`, ()=> { + + describe( 'makeSelector', ()=> { + + it( 'should accept element selector and create camelCase from it', ()=> { + + const selector = 'hello-world'; + expect( resolveDirectiveNameFromSelector( selector ) ).to.equal( 'helloWorld' ); + + } ); + it( 'should accept attribute selector and create camelCase from it', ()=> { + + const selector = '[im-your-father]'; + expect( resolveDirectiveNameFromSelector( selector ) ).to.equal( 'imYourFather' ); + + } ); + it( 'should throw error when not valid element or attribute selector provided', ()=> { + + let selector = 'yabba daba'; + + expect( ()=>resolveDirectiveNameFromSelector( selector ) ).to + .throw( 'Only selectors matching element names or base attributes are supported, got: yabba daba' ); + + } ); + + } ); + describe( `assign`, ()=> { + + it( `should extend object exactly as Object.assign`, ()=> { + + const one = {foo:'yay'}; + const two = {foo:'nay',boo:'low'}; + + const actual = assign(one,two); + const expected = {foo:'nay',boo:'low'}; + + expect( actual ).to.deep.equal( expected ); + expect( assign( {}, { one: 1 }, { two: 2 } ) ).to.deep.equal( { one: 1, two: 2 } ); + + } ); + + } ); + + describe( 'stringify', ()=> { + + it( 'should return name property if it exist on provided type', ()=> { + + function foo() {} + + function boo() { + return 'hello'; + } + + class Moo {} + + expect( stringify( foo ) ).to.equal( 'foo' ); + expect( stringify( boo ) ).to.equal( 'boo' ); + expect( stringify( Moo ) ).to.equal( 'Moo' ); + + } ); + + it( 'should return first line string of function definition if the function is anonymous', ()=> { + + let anonFn = function () {}; + let anonFnMultiLine = function () { + console.log( 'yoo' ); + return null; + }; + + expect( stringify(anonFn) ).to.equal( 'function () { }' ); + expect( stringify(anonFnMultiLine) ).to.equal( 'function () {' ); + + } ); + + it( `should return string of provided type if it isn't a function`, ()=> { + + const obj = { hello: 'world' }; + + expect( stringify( 'hello' ) ).to.equal( 'hello' ); + expect( stringify( null ) ).to.equal( 'null' ); + expect( stringify( undefined ) ).to.equal( 'undefined' ); + expect( stringify( [ 1, 2 ] ) ).to.equal( `1,2` ); + expect( stringify( obj ) ).to.equal( '[object Object]' ); + + } ); + + } ); + + describe( 'hasInjectables', ()=> { + + it( 'should check if Type has $inject as array and its not empty', ()=> { + + class Moo { + static $inject = [ 'hello' ]; + } + class NoMames { + static $inject = []; + } + const obj = { $inject: null }; + const name = 'jabba'; + + expect( hasCtorInjectables( Moo ) ).to.equal( true ); + expect( hasCtorInjectables( NoMames ) ).to.equal( false ); + expect( hasCtorInjectables( obj ) ).to.equal( false ); + expect( hasCtorInjectables( name ) ).to.equal( false ); + + } ); + + + } ); + + describe( 'firstLowerCase', function () { + + it( 'should return string with first char lowercase', ()=> { + + expect( firstToLowerCase( 'JediMaster' ) ).to.equal( 'jediMaster' ); + + } ); + + } ); + describe( 'firstUpperCase', function () { + + it( 'should return string with first char uppercase', ()=> { + + expect( firstToUpperCase( 'jediMaster' ) ).to.equal( 'JediMaster' ); + + } ); + + } ); + + describe( `ES6 Array ponyfills`, ()=> { + + it( `should find array item or undefined if not found`, ()=> { + + let arr:any[] = [ 1, 2, 3, 4, 5 ]; + let found = find( arr, ( el )=> el === 2 ); + expect( found ).equal( 2 ); + + arr = [{name: 'adam'}, {name: 'eve'}, {name: 'john'}]; + found = find(arr, (el)=>el.name === 'eve'); + expect(found).to.deep.equal({name: 'eve'}); + + } ); + it( `should find array item position or -1 if not found`, ()=> { + + const arr = [10, 20, 30, 40]; + + expect( findIndex( arr, ( x )=>x === 30 ) ).to.equal( 2 ); + expect( findIndex( arr, ( x )=>x === 'noop' ) ).to.equal( -1 ); + + } ); + + } ); + +} );