1
- /*! Angular API Mock v0.1.8
1
+ /*! Angular API Mock v0.2.0
2
2
* Licensed with MIT
3
3
* Made with ♥ from Seriema + Redhorn */
4
4
/* Create the main module, `apiMock`. It's the one that needs to be included in
@@ -49,57 +49,102 @@ angular.module('apiMock', [])
49
49
mockDataPath : '/mock_data' ,
50
50
apiPath : '/api' ,
51
51
disable : false ,
52
- stripQueries : true
52
+ stripQueries : true ,
53
+ delay : 0 ,
53
54
} ;
54
55
var fallbacks = [ ] ;
55
56
56
57
// Helper methods
57
58
//
58
59
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 = [ ] ;
61
65
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 ) ;
66
68
} ) ;
67
- return str . join ( '&' ) ;
68
- }
69
69
70
- function queryStringToJSON ( url ) {
71
- var paramString = url . split ( '?' ) [ 1 ] ;
72
- var paramArray = [ ] ;
70
+ return keys ;
71
+ }
73
72
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 ] ) ;
76
78
}
79
+ return keys ;
80
+ }
77
81
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
+ }
83
89
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' : '+' ) ) ;
85
98
}
86
99
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
+ }
90
113
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 ) ) ) ;
94
127
}
95
128
}
96
129
97
- a . sort ( ) ;
130
+ serialize ( params , '' , true ) ;
131
+ return parts . join ( '&' ) ;
132
+ }
98
133
99
- for ( key = 0 ; key < a . length ; key ++ ) {
100
- sorted [ a [ key ] ] = obj [ a [ key ] ] ;
134
+ function queryStringToObject ( paramString ) {
135
+ if ( ! paramString ) {
136
+ return { } ;
101
137
}
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 ;
103
148
}
104
149
105
150
function detectParameter ( keys ) {
@@ -195,43 +240,33 @@ angular.module('apiMock', [])
195
240
}
196
241
197
242
function reroute ( req ) {
198
- var regex ;
199
243
if ( ! isApiPath ( req . url ) ) {
200
244
return req ;
201
245
}
202
246
203
247
// replace apiPath with mockDataPath.
204
248
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 ] || '' ;
221
254
255
+ // query strings are stripped by default (like ?search=banana).
256
+ if ( ! config . stripQueries ) {
222
257
//test if we have query params
223
258
//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
+
227
262
//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 += '/' ;
230
265
}
266
+
231
267
//serialize the param object to convert to string
232
268
//and concatenate to the newPath
233
- newPath = newPath + serialize ( req . params ) ;
234
-
269
+ newPath += angular . lowercase ( jQueryLikeParamSerializer ( params ) ) ;
235
270
}
236
271
237
272
//Kill the params property so they aren't added back on to the end of the url
@@ -265,6 +300,10 @@ angular.module('apiMock', [])
265
300
return fallbacks . length ;
266
301
} ;
267
302
303
+ p . getDelay = function ( ) {
304
+ return config . delay ;
305
+ } ;
306
+
268
307
p . onRequest = function ( req ) {
269
308
if ( config . disable ) {
270
309
return req ;
@@ -327,7 +366,7 @@ angular.module('apiMock', [])
327
366
} ] ;
328
367
} )
329
368
330
- . service ( 'httpInterceptor' , [ "$injector" , "$q" , "apiMock" , function ( $injector , $q , apiMock ) {
369
+ . service ( 'httpInterceptor' , [ "$injector" , "$q" , "$timeout" , " apiMock", function ( $injector , $q , $timeout , apiMock ) {
331
370
/* The main service. Is jacked in as a interceptor on `$http` so it gets called
332
371
* on every http call. This allows us to do our magic. It uses the provider
333
372
* `apiMock` to determine if a mock should be done, then do the actual mocking.
@@ -340,18 +379,39 @@ angular.module('apiMock', [])
340
379
} ;
341
380
342
381
this . response = function ( res ) {
343
- res = apiMock . onResponse ( res ) ;
382
+ var deferred = $q . defer ( ) ;
344
383
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 ;
346
393
} ;
347
394
348
395
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
+ ) ;
354
414
355
- return $q . reject ( rej ) ;
415
+ return deferred . promise ;
356
416
} ;
357
417
} ] ) ;
0 commit comments