|
| 1 | +/** |
| 2 | + * An abstraction on top of ngMockE2E to support async calls using |
| 3 | + * promises. |
| 4 | + * |
| 5 | + * If you need to make an async operation ( such as working |
| 6 | + * with WebSQL ) the orignial ngMockE2E will fall through and you will never |
| 7 | + * have the chance to respond with your own response. |
| 8 | + * |
| 9 | + * ngMockE2EAsync decorates the $httpBackend by utilizing promises. Responses |
| 10 | + * can now be in a form of a promise where the $httpBackend will original function |
| 11 | + * will not be called until your promise has been resolved. Once resolved the original |
| 12 | + * $httpBackend APIs will be called and things will flow their natural ways back to the |
| 13 | + * caller. |
| 14 | + * |
| 15 | + * @ngdoc module |
| 16 | + * @namespace ngMockE2EAsync |
| 17 | + * @author Assaf Moldavsky |
| 18 | + * @version 0.9b |
| 19 | + */ |
| 20 | +(function( ng ) { |
| 21 | + |
| 22 | + var httpMock = ng.module( "ngMockE2EAsync", [ 'ngMockE2E' ] ); |
| 23 | + |
| 24 | + ng.mock.$HttpBackendAsyncDecorator = [ '$rootScope', '$q', '$delegate', '$browser', createHttpBackendAsyncMock ]; |
| 25 | + |
| 26 | + function createHttpBackendAsyncMock( $rootScope, $q, $delegate, $browser ) { |
| 27 | + |
| 28 | + var definnitionsAsync = []; |
| 29 | + |
| 30 | + function $httpBackendAsync( method, url, data, callback, headers, timeout, withCredentials ) { |
| 31 | + |
| 32 | + var d = match( method, url, data, headers ); |
| 33 | + if ( !d || d.passThrough || !d.getPromise ) { |
| 34 | + return $delegate.call(this, method, url, data, callback, headers); |
| 35 | + } |
| 36 | + |
| 37 | + if( !d.getPromise || ! typeof d.getPromise === 'function' ) { |
| 38 | + throw 'unexpected response: ' + d.getPromise; |
| 39 | + } |
| 40 | + |
| 41 | + // we will define an interceptor which will be executed |
| 42 | + // before the actual function is executed. Thsi way |
| 43 | + // we first execute our code and than call the original |
| 44 | + var interceptor = function ( /* arguments we don't care about */ ) { |
| 45 | + |
| 46 | + var whenAsyncConfig = ng.copy( d ); |
| 47 | + delete whenAsyncConfig.getPromise; |
| 48 | + |
| 49 | + var promise = d.getPromise( method, url, data, headers, whenAsyncConfig ); |
| 50 | + |
| 51 | + if( !promise || ( typeof promise.then !== 'function' ) ) { |
| 52 | + throw 'unexpected response: ' + promise; |
| 53 | + } |
| 54 | + |
| 55 | + promise.then( function( response ) { |
| 56 | + |
| 57 | + if( !response ) { |
| 58 | + throw 'response was unexpected: ' + response; |
| 59 | + } |
| 60 | + |
| 61 | + // callback is the orignial function which we are wrapping / decorating |
| 62 | + callback.apply( this, response ); |
| 63 | + |
| 64 | + }); |
| 65 | + |
| 66 | + }; |
| 67 | + |
| 68 | + return $delegate.call(this, method, url, data, interceptor, headers); |
| 69 | + |
| 70 | + } |
| 71 | + |
| 72 | + // copy all existing APIs from the $delegate into the decorator |
| 73 | + for( var key in $delegate ) { |
| 74 | + $httpBackendAsync[ key ] = $delegate[ key ]; |
| 75 | + } |
| 76 | + |
| 77 | + // matchers just like in AgnularJS itself |
| 78 | + function match( method, url, data, headers ) { |
| 79 | + |
| 80 | + var defs = definnitionsAsync; |
| 81 | + |
| 82 | + for (var i = -1; ++i < defs.length;) { |
| 83 | + |
| 84 | + var def = definnitionsAsync[i]; |
| 85 | + |
| 86 | + if ( def.method.toUpperCase() === method.toUpperCase() ) { |
| 87 | + if ( matchUrl( def.url, url ) |
| 88 | + && (!angular.isDefined( data ) || matchData( def.data, data )) |
| 89 | + && (!angular.isDefined( headers ) || matchHeaders( def.headers, headers ))) { |
| 90 | + |
| 91 | + return def; |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + }; |
| 96 | + |
| 97 | + function matchUrl( url, u ) { |
| 98 | + |
| 99 | + if (!url) return true; |
| 100 | + if (angular.isFunction(url.test))return url.test(u); |
| 101 | + if (angular.isFunction(url))return url(u); |
| 102 | + return url == u; |
| 103 | + |
| 104 | + }; |
| 105 | + function matchHeaders( headers, h ) { |
| 106 | + |
| 107 | + if (angular.isUndefined(headers))return true; |
| 108 | + if (angular.isFunction(headers)) return headers(h); |
| 109 | + return angular.equals(headers, h); |
| 110 | + |
| 111 | + }; |
| 112 | + function matchData( data, d ) { |
| 113 | + |
| 114 | + if (angular.isUndefined(data)) return true; |
| 115 | + if (data && angular.isFunction(data.test)) return data.test(d); |
| 116 | + if (data && angular.isFunction(data)) return data(d); |
| 117 | + if (data && !angular.isString(data)) { |
| 118 | + return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); |
| 119 | + } |
| 120 | + return ( data == d ); |
| 121 | + |
| 122 | + }; |
| 123 | + |
| 124 | + // decorate $httpBackend#when() and $httpBackend#expect() |
| 125 | + function argumentsToArray( arguments ) { |
| 126 | + var array = []; |
| 127 | + for( var i = 0, len = arguments.length; i < len; i++ ) { |
| 128 | + array.push( arguments[i] ); |
| 129 | + } |
| 130 | + |
| 131 | + return array; |
| 132 | + } |
| 133 | + $httpBackendAsync.whenAsync = function() { |
| 134 | + return whenAsync.apply( $httpBackendAsync, [ $delegate.when ].concat( argumentsToArray( arguments ) ) ); |
| 135 | + }; |
| 136 | + $httpBackendAsync.expectAsync = function() { |
| 137 | + return expectAsync.apply( $httpBackendAsync, [ $delegate.expect ].concat( argumentsToArray( arguments ) ) ); |
| 138 | + }; |
| 139 | + |
| 140 | + var expectAsync = whenAsync; |
| 141 | + function whenAsync( deletageMethod, method, url, data, headers, keys ) { |
| 142 | + |
| 143 | + function MockHttpExpectation( method, url, data, headers, keys, promiseFn ) { |
| 144 | + |
| 145 | + this.method = method; |
| 146 | + this.url = url; |
| 147 | + this.data = data; |
| 148 | + this.header = headers; |
| 149 | + this.keys = keys; |
| 150 | + this.getPromise = promiseFn; // this will be called in our decorated constructor when the $http provider is used |
| 151 | + |
| 152 | + }; |
| 153 | + |
| 154 | + var def = new MockHttpExpectation( method, url, data, headers, keys, null ); |
| 155 | + var chain = deletageMethod.call( $delegate, method, url, data, headers ); |
| 156 | + |
| 157 | + var ret = { |
| 158 | + respond: function ( response ) { |
| 159 | + |
| 160 | + // we want to differentiate between a function and a promise |
| 161 | + |
| 162 | + if( response ) { |
| 163 | + |
| 164 | + if( typeof response === 'function' ) { |
| 165 | + |
| 166 | + // we ASSUME that when executed this function will return a promise |
| 167 | + def.getPromise = response; |
| 168 | + |
| 169 | + } else if( response.then && ( typeof response.then === 'function' ) ) { |
| 170 | + |
| 171 | + // we got a raw promise, we need to wrap it in a function |
| 172 | + def.getPromise = function( method, url, data, headers, keys ) { |
| 173 | + return response; |
| 174 | + }; |
| 175 | + |
| 176 | + } else { |
| 177 | + |
| 178 | + throw 'unexpected response ' + response; |
| 179 | + |
| 180 | + } |
| 181 | + |
| 182 | + // call the real function |
| 183 | + chain.respond.apply( chain, function() {} ); |
| 184 | + |
| 185 | + } else { |
| 186 | + // call the real function |
| 187 | + chain.respond.apply( chain, arguments ); |
| 188 | + } |
| 189 | + |
| 190 | + return ret; |
| 191 | + }, |
| 192 | + passThrough: function () { // TODO: Assaf: not sure what to do with passthrough for now |
| 193 | + /* |
| 194 | + // the def used be have these parameters: [ method, url, data, headers, 0, undefined ]; |
| 195 | + |
| 196 | + def[4] = 0; |
| 197 | + def[5] = true; |
| 198 | + chain.passThrough.apply(chain); |
| 199 | + return ret; |
| 200 | + */ |
| 201 | + } |
| 202 | + }; |
| 203 | + |
| 204 | + definnitionsAsync.push( def ); |
| 205 | + return ret; |
| 206 | + }; |
| 207 | + |
| 208 | + return $httpBackendAsync; |
| 209 | + |
| 210 | + } |
| 211 | + |
| 212 | + httpMock.config(['$provide',function( $provide ) { |
| 213 | + |
| 214 | + $provide.decorator( '$httpBackend', angular.mock.$HttpBackendAsyncDecorator ); |
| 215 | + |
| 216 | + }]); |
| 217 | + |
| 218 | + |
| 219 | +})( angular ); |
0 commit comments