1- /*! Angular API Mock v0.1.8
1+ /*! Angular API Mock v0.2.0
22 * Licensed with MIT
33 * Made with ♥ from Seriema + Redhorn */
44/* Create the main module, `apiMock`. It's the one that needs to be included in
@@ -49,57 +49,102 @@ angular.module('apiMock', [])
4949 mockDataPath : '/mock_data' ,
5050 apiPath : '/api' ,
5151 disable : false ,
52- stripQueries : true
52+ stripQueries : true ,
53+ delay : 0 ,
5354 } ;
5455 var fallbacks = [ ] ;
5556
5657 // Helper methods
5758 //
5859
59- function serialize ( obj ) {
60- var str = [ ] ;
60+ // TODO: IE8: remove when we drop IE8/Angular 1.2 support.
61+ // Object.keys isn't supported in IE8. Which we need to support as long as we support Angular 1.2.
62+ // This isn't a complete polyfill! It's just enough for what we need (and we don't need to bloat).
63+ function objectKeys ( object ) {
64+ var keys = [ ] ;
6165
62- obj = sortObjPropertiesAlpha ( obj ) ;
63- angular . forEach ( obj , function ( value , p ) {
64- var encodedValue = encodeURIComponent ( value ) ;
65- str . push ( encodeURIComponent ( p ) + '=' + encodedValue ) ;
66+ angular . forEach ( object , function ( value , key ) {
67+ keys . push ( key ) ;
6668 } ) ;
67- return str . join ( '&' ) ;
68- }
6969
70- function queryStringToJSON ( url ) {
71- var paramString = url . split ( '?' ) [ 1 ] ;
72- var paramArray = [ ] ;
70+ return keys ;
71+ }
7372
74- if ( paramString ) {
75- paramArray = paramString . split ( '&' ) ;
73+ // Taken from Angular 1.4.x: https://github.com/angular/angular.js/blob/f13852c179ffd9ec18b7a94df27dec39eb5f19fc/src/Angular.js#L296
74+ function forEachSorted ( obj , iterator , context ) {
75+ var keys = objectKeys ( obj ) . sort ( ) ;
76+ for ( var i = 0 ; i < keys . length ; i ++ ) {
77+ iterator . call ( context , obj [ keys [ i ] ] , keys [ i ] ) ;
7678 }
79+ return keys ;
80+ }
7781
78- var result = { } ;
79- paramArray . forEach ( function ( param ) {
80- param = param . split ( '=' ) ;
81- result [ param [ 0 ] ] = decodeURIComponent ( param [ 1 ] || '' ) ;
82- } ) ;
82+ // Taken from Angular 1.4.x: https://github.com/angular/angular.js/blob/929ec6ba5a60e926654583033a90aebe716123c0/src/ng/http.js#L18
83+ function serializeValue ( v ) {
84+ if ( angular . isObject ( v ) ) {
85+ return angular . isDate ( v ) ? v . toISOString ( ) : angular . toJson ( v ) ;
86+ }
87+ return v ;
88+ }
8389
84- return JSON . parse ( JSON . stringify ( result ) ) ;
90+ // Taken from Angular 1.4.x: https://github.com/angular/angular.js/blob/720012eab6fef5e075a1d6876dd2e508c8e95b73/src/ngResource/resource.js#L405
91+ function encodeUriQuery ( val , pctEncodeSpaces ) {
92+ return encodeURIComponent ( val ) .
93+ replace ( / % 4 0 / gi, '@' ) .
94+ replace ( / % 3 A / gi, ':' ) .
95+ replace ( / % 2 4 / g, '$' ) .
96+ replace ( / % 2 C / gi, ',' ) .
97+ replace ( / % 2 0 / g, ( pctEncodeSpaces ? '%20' : '+' ) ) ;
8598 }
8699
87- function sortObjPropertiesAlpha ( obj ) {
88- var sorted = { } ,
89- key , a = [ ] ;
100+ // TODO: replace with a $httpParamSerializerJQLikeProvider() call when we require Angular 1.4 (i.e. when we drop 1.2 and 1.3).
101+ // Taken from Angular 1.4.x: https://github.com/angular/angular.js/blob/929ec6ba5a60e926654583033a90aebe716123c0/src/ng/http.js#L108
102+ function jQueryLikeParamSerializer ( params ) {
103+ if ( ! params ) {
104+ return '' ;
105+ }
106+
107+ var parts = [ ] ;
108+
109+ function serialize ( toSerialize , prefix , topLevel ) {
110+ if ( toSerialize === null || angular . isUndefined ( toSerialize ) ) {
111+ return ;
112+ }
90113
91- for ( key in obj ) {
92- if ( obj . hasOwnProperty ( key ) ) {
93- a . push ( key ) ;
114+ if ( angular . isArray ( toSerialize ) ) {
115+ angular . forEach ( toSerialize , function ( value , index ) {
116+ serialize ( value , prefix + '[' + ( angular . isObject ( value ) ? index : '' ) + ']' ) ;
117+ } ) ;
118+ } else if ( angular . isObject ( toSerialize ) && ! angular . isDate ( toSerialize ) ) {
119+ forEachSorted ( toSerialize , function ( value , key ) {
120+ serialize ( value , prefix +
121+ ( topLevel ? '' : '[' ) +
122+ key +
123+ ( topLevel ? '' : ']' ) ) ;
124+ } ) ;
125+ } else {
126+ parts . push ( encodeUriQuery ( prefix ) + '=' + encodeUriQuery ( serializeValue ( toSerialize ) ) ) ;
94127 }
95128 }
96129
97- a . sort ( ) ;
130+ serialize ( params , '' , true ) ;
131+ return parts . join ( '&' ) ;
132+ }
98133
99- for ( key = 0 ; key < a . length ; key ++ ) {
100- sorted [ a [ key ] ] = obj [ a [ key ] ] ;
134+ function queryStringToObject ( paramString ) {
135+ if ( ! paramString ) {
136+ return { } ;
101137 }
102- return sorted ;
138+
139+ var paramArray = paramString . split ( '&' ) ;
140+
141+ var result = { } ;
142+ angular . forEach ( paramArray , function ( param ) {
143+ param = param . split ( '=' ) ;
144+ result [ param [ 0 ] ] = param [ 1 ] || '' ;
145+ } ) ;
146+
147+ return result ;
103148 }
104149
105150 function detectParameter ( keys ) {
@@ -195,43 +240,33 @@ angular.module('apiMock', [])
195240 }
196241
197242 function reroute ( req ) {
198- var regex ;
199243 if ( ! isApiPath ( req . url ) ) {
200244 return req ;
201245 }
202246
203247 // replace apiPath with mockDataPath.
204248 var oldPath = req . url ;
205- var newPath = req . url . substring ( config . apiPath . length ) ;
206- newPath = config . mockDataPath + newPath ;
207-
208- if ( config . stripQueries ) {
209- // strip query strings (like ?search=banana).
210- regex = / [ a - z A - z 0 - 9 / . \- ] * / ;
211- newPath = regex . exec ( newPath ) [ 0 ] ;
212- } else {
213- var queryParamsFromUrl = queryStringToJSON ( newPath ) ;
214- //if req.params is an object leave it as is but if it isn't then
215- //normalize it to an empty object so we can cleanly merge it with queryParamsFromUrl
216- req . params = typeof req . params === 'object' ? req . params : { } ;
217-
218- // strip query strings (like ?search=banana).
219- regex = / [ a - z A - z 0 - 9 / . \- ] * / ;
220- newPath = regex . exec ( newPath ) [ 0 ] ;
249+ var redirectedPath = req . url . replace ( config . apiPath , config . mockDataPath ) ;
250+
251+ var split = redirectedPath . split ( '?' ) ;
252+ var newPath = split [ 0 ] ;
253+ var queries = split [ 1 ] || '' ;
221254
255+ // query strings are stripped by default (like ?search=banana).
256+ if ( ! config . stripQueries ) {
222257 //test if we have query params
223258 //if we do merge them on to the params object
224- if ( typeof queryParamsFromUrl === 'object' ) {
225- req . params = angular . extend ( req . params , queryParamsFromUrl ) ;
226- }
259+ var queryParamsFromUrl = queryStringToObject ( queries ) ;
260+ var params = angular . extend ( req . params || { } , queryParamsFromUrl ) ;
261+
227262 //test if there is already a trailing /
228- if ( newPath . substring ( newPath . length - 1 ) !== '/' ) {
229- newPath = newPath + '/' ;
263+ if ( newPath [ newPath . length - 1 ] !== '/' ) {
264+ newPath += '/' ;
230265 }
266+
231267 //serialize the param object to convert to string
232268 //and concatenate to the newPath
233- newPath = newPath + serialize ( req . params ) ;
234-
269+ newPath += angular . lowercase ( jQueryLikeParamSerializer ( params ) ) ;
235270 }
236271
237272 //Kill the params property so they aren't added back on to the end of the url
@@ -265,6 +300,10 @@ angular.module('apiMock', [])
265300 return fallbacks . length ;
266301 } ;
267302
303+ p . getDelay = function ( ) {
304+ return config . delay ;
305+ } ;
306+
268307 p . onRequest = function ( req ) {
269308 if ( config . disable ) {
270309 return req ;
@@ -327,7 +366,7 @@ angular.module('apiMock', [])
327366 } ] ;
328367 } )
329368
330- . service ( 'httpInterceptor' , [ "$injector" , "$q" , "apiMock" , function ( $injector , $q , apiMock ) {
369+ . service ( 'httpInterceptor' , [ "$injector" , "$q" , "$timeout" , " apiMock", function ( $injector , $q , $timeout , apiMock ) {
331370 /* The main service. Is jacked in as a interceptor on `$http` so it gets called
332371 * on every http call. This allows us to do our magic. It uses the provider
333372 * `apiMock` to determine if a mock should be done, then do the actual mocking.
@@ -340,18 +379,39 @@ angular.module('apiMock', [])
340379 } ;
341380
342381 this . response = function ( res ) {
343- res = apiMock . onResponse ( res ) ;
382+ var deferred = $q . defer ( ) ;
344383
345- return res || $q . when ( res ) ;
384+ $timeout (
385+ function ( ) {
386+ deferred . resolve ( apiMock . onResponse ( res ) ) ; // TODO: Apparently, no tests break regardless what this resolves to. Fix the tests!
387+ } ,
388+ apiMock . getDelay ( ) ,
389+ true // Trigger a $digest.
390+ ) ;
391+
392+ return deferred . promise ;
346393 } ;
347394
348395 this . responseError = function ( rej ) {
349- var recover = apiMock . recover ( rej ) ;
350- if ( recover ) {
351- var $http = $injector . get ( '$http' ) ;
352- return $http ( recover ) ;
353- }
396+ var deferred = $q . defer ( ) ;
397+
398+ $timeout (
399+ function ( ) {
400+ var recover = apiMock . recover ( rej ) ;
401+
402+ if ( recover ) {
403+ var $http = $injector . get ( '$http' ) ;
404+ $http ( recover ) . then ( function ( data ) {
405+ deferred . resolve ( data ) ;
406+ } ) ;
407+ } else {
408+ deferred . reject ( rej ) ;
409+ }
410+ } ,
411+ apiMock . getDelay ( ) ,
412+ true // Trigger a $digest.
413+ ) ;
354414
355- return $q . reject ( rej ) ;
415+ return deferred . promise ;
356416 } ;
357417 } ] ) ;
0 commit comments