nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components / jqTree / static / bower_components / jquery-mockjax / dist / jquery.mockjax.js
1 /*! jQuery Mockjax
2  * A Plugin providing simple and flexible mocking of ajax requests and responses
3  * 
4  * Version: 2.2.1
5  * Home: https://github.com/jakerella/jquery-mockjax
6  * Copyright (c) 2016 Jordan Kasper, formerly appendTo;
7  * NOTE: This repository was taken over by Jordan Kasper (@jakerella) October, 2014
8  * 
9  * Dual licensed under the MIT or GPL licenses.
10  * http://opensource.org/licenses/MIT OR http://www.gnu.org/licenses/gpl-2.0.html
11  */
12 (function(root, factory) {
13         'use strict';
14
15         // AMDJS module definition
16         if ( typeof define === 'function' && define.amd && define.amd.jQuery ) {
17                 define(['jquery'], function($) {
18                         return factory($, root);
19                 });
20
21         // CommonJS module definition
22         } else if ( typeof exports === 'object') {
23
24                 // NOTE: To use Mockjax as a Node module you MUST provide the factory with
25                 // a valid version of jQuery and a window object (the global scope):
26                 // var mockjax = require('jquery.mockjax')(jQuery, window);
27
28                 module.exports = factory;
29
30         // Global jQuery in web browsers
31         } else {
32                 return factory(root.jQuery || root.$, root);
33         }
34 }(this, function($, window) {
35         'use strict';
36
37         var _ajax = $.ajax,
38                 mockHandlers = [],
39                 mockedAjaxCalls = [],
40                 unmockedAjaxCalls = [],
41                 CALLBACK_REGEX = /=\?(&|$)/,
42                 jsc = (new Date()).getTime(),
43                 DEFAULT_RESPONSE_TIME = 500;
44
45         // Parse the given XML string.
46         function parseXML(xml) {
47                 if ( window.DOMParser === undefined && window.ActiveXObject ) {
48                         window.DOMParser = function() { };
49                         DOMParser.prototype.parseFromString = function( xmlString ) {
50                                 var doc = new ActiveXObject('Microsoft.XMLDOM');
51                                 doc.async = 'false';
52                                 doc.loadXML( xmlString );
53                                 return doc;
54                         };
55                 }
56
57                 try {
58                         var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
59                         if ( $.isXMLDoc( xmlDoc ) ) {
60                                 var err = $('parsererror', xmlDoc);
61                                 if ( err.length === 1 ) {
62                                         throw new Error('Error: ' + $(xmlDoc).text() );
63                                 }
64                         } else {
65                                 throw new Error('Unable to parse XML');
66                         }
67                         return xmlDoc;
68                 } catch( e ) {
69                         var msg = ( e.name === undefined ? e : e.name + ': ' + e.message );
70                         $(document).trigger('xmlParseError', [ msg ]);
71                         return undefined;
72                 }
73         }
74
75         // Check if the data field on the mock handler and the request match. This
76         // can be used to restrict a mock handler to being used only when a certain
77         // set of data is passed to it.
78         function isMockDataEqual( mock, live ) {
79                 logger.debug( mock, ['Checking mock data against request data', mock, live] );
80                 var identical = true;
81
82                 if ( $.isFunction(mock) ) {
83                         return !!mock(live);
84                 }
85
86                 // Test for situations where the data is a querystring (not an object)
87                 if (typeof live === 'string') {
88                         // Querystring may be a regex
89                         if ($.isFunction( mock.test )) {
90                                 return mock.test(live);
91                         } else if (typeof mock === 'object') {
92                                 live = getQueryParams(live);
93                         } else {
94                                 return mock === live;
95                         }
96                 }
97
98                 $.each(mock, function(k) {
99                         if ( live[k] === undefined ) {
100                                 identical = false;
101                                 return identical;
102                         } else {
103                                 if ( typeof live[k] === 'object' && live[k] !== null ) {
104                                         if ( identical && $.isArray( live[k] ) ) {
105                                                 identical = $.isArray( mock[k] ) && live[k].length === mock[k].length;
106                                         }
107                                         identical = identical && isMockDataEqual(mock[k], live[k]);
108                                 } else {
109                                         if ( mock[k] && $.isFunction( mock[k].test ) ) {
110                                                 identical = identical && mock[k].test(live[k]);
111                                         } else {
112                                                 identical = identical && ( mock[k] === live[k] );
113                                         }
114                                 }
115                         }
116                 });
117
118                 return identical;
119         }
120
121         function getQueryParams(queryString) {
122                 var i, l, param, tmp,
123                         paramsObj = {},
124                         params = String(queryString).split(/&/);
125
126                 for (i=0, l=params.length; i<l; ++i) {
127                         param = params[i];
128                         try {
129                                 param = decodeURIComponent(param.replace(/\+/g, ' '));
130                                 param = param.split(/=/);
131                         } catch(e) {
132                                 // Can't parse this one, so let it go?
133                                 continue;
134                         }
135
136                         if (paramsObj[param[0]]) {
137                                 // this is an array query param (more than one entry in query)
138                                 if (!paramsObj[param[0]].splice) {
139                                         // if not already an array, make it one
140                                         tmp = paramsObj[param[0]];
141                                         paramsObj[param[0]] = [];
142                                         paramsObj[param[0]].push(tmp);
143                                 }
144                                 paramsObj[param[0]].push(param[1]);
145                         } else {
146                                 paramsObj[param[0]] = param[1];
147                         }
148                 }
149
150                 logger.debug( null, ['Getting query params from string', queryString, paramsObj] );
151
152                 return paramsObj;
153         }
154
155         // See if a mock handler property matches the default settings
156         function isDefaultSetting(handler, property) {
157                 return handler[property] === $.mockjaxSettings[property];
158         }
159
160         // Check the given handler should mock the given request
161         function getMockForRequest( handler, requestSettings ) {
162                 // If the mock was registered with a function, let the function decide if we
163                 // want to mock this request
164                 if ( $.isFunction(handler) ) {
165                         return handler( requestSettings );
166                 }
167
168                 // Inspect the URL of the request and check if the mock handler's url
169                 // matches the url for this ajax request
170                 if ( $.isFunction(handler.url.test) ) {
171                         // The user provided a regex for the url, test it
172                         if ( !handler.url.test( requestSettings.url ) ) {
173                                 return null;
174                         }
175                 } else {
176
177                         // Apply namespace prefix to the mock handler's url.
178                         var namespace = handler.namespace || $.mockjaxSettings.namespace;
179                         if (!!namespace) {
180                                 var namespacedUrl = [namespace, handler.url].join('/');
181                                 namespacedUrl = namespacedUrl.replace(/(\/+)/g, '/');
182                                 handler.url = namespacedUrl;
183                         }
184
185                         // Look for a simple wildcard '*' or a direct URL match
186                         var star = handler.url.indexOf('*');
187                         if (handler.url !== requestSettings.url && star === -1 ||
188                                         !new RegExp(handler.url.replace(/[-[\]{}()+?.,\\^$|#\s]/g, '\\$&').replace(/\*/g, '.+')).test(requestSettings.url)) {
189                                 return null;
190                         }
191                 }
192
193                 // Inspect the request headers submitted
194                 if ( handler.requestHeaders ) {
195                         //No expectation for headers, do not mock this request
196                         if (requestSettings.headers === undefined) {
197                                 return null;
198                         } else {
199                                 var headersMismatch = false;
200                                 $.each(handler.requestHeaders, function(key, value) {
201                                         var v = requestSettings.headers[key];
202                                         if(v !== value) {
203                                                 headersMismatch = true;
204                                                 return false;
205                                         }
206                                 });
207                                 //Headers do not match, do not mock this request
208                                 if (headersMismatch) {
209                                         return null;
210                                 }
211                         }
212                 }
213
214                 // Inspect the data submitted in the request (either POST body or GET query string)
215                 if ( handler.data ) {
216                         if ( !requestSettings.data || !isMockDataEqual(handler.data, requestSettings.data) ) {
217                                 // They're not identical, do not mock this request
218                                 return null;
219                         }
220                 }
221                 // Inspect the request type
222                 if ( handler && handler.type &&
223                                 handler.type.toLowerCase() !== requestSettings.type.toLowerCase() ) {
224                         // The request type doesn't match (GET vs. POST)
225                         return null;
226                 }
227
228                 return handler;
229         }
230
231         function isPosNum(value) {
232                 return typeof value === 'number' && value >= 0;
233         }
234
235         function parseResponseTimeOpt(responseTime) {
236                 if ($.isArray(responseTime) && responseTime.length === 2) {
237                         var min = responseTime[0];
238                         var max = responseTime[1];
239                         if(isPosNum(min) && isPosNum(max)) {
240                                 return Math.floor(Math.random() * (max - min)) + min;
241                         }
242                 } else if(isPosNum(responseTime)) {
243                         return responseTime;
244                 }
245                 return DEFAULT_RESPONSE_TIME;
246         }
247
248         // Process the xhr objects send operation
249         function _xhrSend(mockHandler, requestSettings, origSettings) {
250                 logger.debug( mockHandler, ['Sending fake XHR request', mockHandler, requestSettings, origSettings] );
251
252                 // This is a substitute for < 1.4 which lacks $.proxy
253                 var process = (function(that) {
254                         return function() {
255                                 return (function() {
256                                         // The request has returned
257                                         this.status = mockHandler.status;
258                                         this.statusText = mockHandler.statusText;
259                                         this.readyState = 1;
260
261                                         var finishRequest = function () {
262                                                 this.readyState = 4;
263
264                                                 var onReady;
265                                                 // Copy over our mock to our xhr object before passing control back to
266                                                 // jQuery's onreadystatechange callback
267                                                 if ( requestSettings.dataType === 'json' && ( typeof mockHandler.responseText === 'object' ) ) {
268                                                         this.responseText = JSON.stringify(mockHandler.responseText);
269                                                 } else if ( requestSettings.dataType === 'xml' ) {
270                                                         if ( typeof mockHandler.responseXML === 'string' ) {
271                                                                 this.responseXML = parseXML(mockHandler.responseXML);
272                                                                 //in jQuery 1.9.1+, responseXML is processed differently and relies on responseText
273                                                                 this.responseText = mockHandler.responseXML;
274                                                         } else {
275                                                                 this.responseXML = mockHandler.responseXML;
276                                                         }
277                                                 } else if (typeof mockHandler.responseText === 'object' && mockHandler.responseText !== null) {
278                                                         // since jQuery 1.9 responseText type has to match contentType
279                                                         mockHandler.contentType = 'application/json';
280                                                         this.responseText = JSON.stringify(mockHandler.responseText);
281                                                 } else {
282                                                         this.responseText = mockHandler.responseText;
283                                                 }
284                                                 if( typeof mockHandler.status === 'number' || typeof mockHandler.status === 'string' ) {
285                                                         this.status = mockHandler.status;
286                                                 }
287                                                 if( typeof mockHandler.statusText === 'string') {
288                                                         this.statusText = mockHandler.statusText;
289                                                 }
290                                                 // jQuery 2.0 renamed onreadystatechange to onload
291                                                 onReady = this.onload || this.onreadystatechange;
292
293                                                 // jQuery < 1.4 doesn't have onreadystate change for xhr
294                                                 if ( $.isFunction( onReady ) ) {
295                                                         if( mockHandler.isTimeout) {
296                                                                 this.status = -1;
297                                                         }
298                                                         onReady.call( this, mockHandler.isTimeout ? 'timeout' : undefined );
299                                                 } else if ( mockHandler.isTimeout ) {
300                                                         // Fix for 1.3.2 timeout to keep success from firing.
301                                                         this.status = -1;
302                                                 }
303                                         };
304
305                                         // We have an executable function, call it to give
306                                         // the mock handler a chance to update it's data
307                                         if ( $.isFunction(mockHandler.response) ) {
308                                                 // Wait for it to finish
309                                                 if ( mockHandler.response.length === 2 ) {
310                                                         mockHandler.response(origSettings, function () {
311                                                                 finishRequest.call(that);
312                                                         });
313                                                         return;
314                                                 } else {
315                                                         mockHandler.response(origSettings);
316                                                 }
317                                         }
318
319                                         finishRequest.call(that);
320                                 }).apply(that);
321                         };
322                 })(this);
323
324                 if ( mockHandler.proxy ) {
325                         logger.info( mockHandler, ['Retrieving proxy file: ' + mockHandler.proxy, mockHandler] );
326                         // We're proxying this request and loading in an external file instead
327                         _ajax({
328                                 global: false,
329                                 url: mockHandler.proxy,
330                                 type: mockHandler.proxyType,
331                                 data: mockHandler.data,
332                                 async: requestSettings.async,
333                                 dataType: requestSettings.dataType === 'script' ? 'text/plain' : requestSettings.dataType,
334                                 complete: function(xhr) {
335                                         // Fix for bug #105
336                                         // jQuery will convert the text to XML for us, and if we use the actual responseXML here
337                                         // then some other things don't happen, resulting in no data given to the 'success' cb
338                                         mockHandler.responseXML = mockHandler.responseText = xhr.responseText;
339
340                                         // Don't override the handler status/statusText if it's specified by the config
341                                         if (isDefaultSetting(mockHandler, 'status')) {
342                                                 mockHandler.status = xhr.status;
343                                         }
344                                         if (isDefaultSetting(mockHandler, 'statusText')) {
345                                                 mockHandler.statusText = xhr.statusText;
346                                         }
347
348                                         if ( requestSettings.async === false ) {
349                                                 // TODO: Blocking delay
350                                                 process();
351                                         } else {
352                                                 this.responseTimer = setTimeout(process, parseResponseTimeOpt(mockHandler.responseTime));
353                                         }
354                                 }
355                         });
356                 } else {
357                         // type === 'POST' || 'GET' || 'DELETE'
358                         if ( requestSettings.async === false ) {
359                                 // TODO: Blocking delay
360                                 process();
361                         } else {
362                                 this.responseTimer = setTimeout(process, parseResponseTimeOpt(mockHandler.responseTime));
363                         }
364                 }
365
366         }
367
368         // Construct a mocked XHR Object
369         function xhr(mockHandler, requestSettings, origSettings, origHandler) {
370                 logger.debug( mockHandler, ['Creating new mock XHR object', mockHandler, requestSettings, origSettings, origHandler] );
371
372                 // Extend with our default mockjax settings
373                 mockHandler = $.extend(true, {}, $.mockjaxSettings, mockHandler);
374
375                 if (typeof mockHandler.headers === 'undefined') {
376                         mockHandler.headers = {};
377                 }
378                 if (typeof requestSettings.headers === 'undefined') {
379                         requestSettings.headers = {};
380                 }
381                 if ( mockHandler.contentType ) {
382                         mockHandler.headers['content-type'] = mockHandler.contentType;
383                 }
384
385                 return {
386                         status: mockHandler.status,
387                         statusText: mockHandler.statusText,
388                         readyState: 1,
389                         open: function() { },
390                         send: function() {
391                                 origHandler.fired = true;
392                                 _xhrSend.call(this, mockHandler, requestSettings, origSettings);
393                         },
394                         abort: function() {
395                                 clearTimeout(this.responseTimer);
396                         },
397                         setRequestHeader: function(header, value) {
398                                 requestSettings.headers[header] = value;
399                         },
400                         getResponseHeader: function(header) {
401                                 // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
402                                 if ( mockHandler.headers && mockHandler.headers[header] ) {
403                                         // Return arbitrary headers
404                                         return mockHandler.headers[header];
405                                 } else if ( header.toLowerCase() === 'last-modified' ) {
406                                         return mockHandler.lastModified || (new Date()).toString();
407                                 } else if ( header.toLowerCase() === 'etag' ) {
408                                         return mockHandler.etag || '';
409                                 } else if ( header.toLowerCase() === 'content-type' ) {
410                                         return mockHandler.contentType || 'text/plain';
411                                 }
412                         },
413                         getAllResponseHeaders: function() {
414                                 var headers = '';
415                                 // since jQuery 1.9 responseText type has to match contentType
416                                 if (mockHandler.contentType) {
417                                         mockHandler.headers['Content-Type'] = mockHandler.contentType;
418                                 }
419                                 $.each(mockHandler.headers, function(k, v) {
420                                         headers += k + ': ' + v + '\n';
421                                 });
422                                 return headers;
423                         }
424                 };
425         }
426
427         // Process a JSONP mock request.
428         function processJsonpMock( requestSettings, mockHandler, origSettings ) {
429                 // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
430                 // because there isn't an easy hook for the cross domain script tag of jsonp
431
432                 processJsonpUrl( requestSettings );
433
434                 requestSettings.dataType = 'json';
435                 if(requestSettings.data && CALLBACK_REGEX.test(requestSettings.data) || CALLBACK_REGEX.test(requestSettings.url)) {
436                         createJsonpCallback(requestSettings, mockHandler, origSettings);
437
438                         // We need to make sure
439                         // that a JSONP style response is executed properly
440
441                         var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
442                                 parts = rurl.exec( requestSettings.url ),
443                                 remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
444
445                         requestSettings.dataType = 'script';
446                         if(requestSettings.type.toUpperCase() === 'GET' && remote ) {
447                                 var newMockReturn = processJsonpRequest( requestSettings, mockHandler, origSettings );
448
449                                 // Check if we are supposed to return a Deferred back to the mock call, or just
450                                 // signal success
451                                 if(newMockReturn) {
452                                         return newMockReturn;
453                                 } else {
454                                         return true;
455                                 }
456                         }
457                 }
458                 return null;
459         }
460
461         // Append the required callback parameter to the end of the request URL, for a JSONP request
462         function processJsonpUrl( requestSettings ) {
463                 if ( requestSettings.type.toUpperCase() === 'GET' ) {
464                         if ( !CALLBACK_REGEX.test( requestSettings.url ) ) {
465                                 requestSettings.url += (/\?/.test( requestSettings.url ) ? '&' : '?') +
466                                         (requestSettings.jsonp || 'callback') + '=?';
467                         }
468                 } else if ( !requestSettings.data || !CALLBACK_REGEX.test(requestSettings.data) ) {
469                         requestSettings.data = (requestSettings.data ? requestSettings.data + '&' : '') + (requestSettings.jsonp || 'callback') + '=?';
470                 }
471         }
472
473         // Process a JSONP request by evaluating the mocked response text
474         function processJsonpRequest( requestSettings, mockHandler, origSettings ) {
475                 logger.debug( mockHandler, ['Performing JSONP request', mockHandler, requestSettings, origSettings] );
476
477                 // Synthesize the mock request for adding a script tag
478                 var callbackContext = origSettings && origSettings.context || requestSettings,
479                         // If we are running under jQuery 1.5+, return a deferred object
480                         newMock = ($.Deferred) ? (new $.Deferred()) : null;
481
482                 // If the response handler on the moock is a function, call it
483                 if ( mockHandler.response && $.isFunction(mockHandler.response) ) {
484
485                         mockHandler.response(origSettings);
486
487
488                 } else if ( typeof mockHandler.responseText === 'object' ) {
489                         // Evaluate the responseText javascript in a global context
490                         $.globalEval( '(' + JSON.stringify( mockHandler.responseText ) + ')');
491
492                 } else if (mockHandler.proxy) {
493                         logger.info( mockHandler, ['Performing JSONP proxy request: ' + mockHandler.proxy, mockHandler] );
494
495                         // This handles the unique case where we have a remote URL, but want to proxy the JSONP
496                         // response to another file (not the same URL as the mock matching)
497                         _ajax({
498                                 global: false,
499                                 url: mockHandler.proxy,
500                                 type: mockHandler.proxyType,
501                                 data: mockHandler.data,
502                                 dataType: requestSettings.dataType === 'script' ? 'text/plain' : requestSettings.dataType,
503                                 complete: function(xhr) {
504                                         $.globalEval( '(' + xhr.responseText + ')');
505                                         completeJsonpCall( requestSettings, mockHandler, callbackContext, newMock );
506                                 }
507                         });
508
509                         return newMock;
510
511                 } else {
512                         $.globalEval( '(' +
513                                 ((typeof mockHandler.responseText === 'string') ?
514                                         ('"' + mockHandler.responseText + '"') : mockHandler.responseText) +
515                         ')');
516                 }
517
518                 completeJsonpCall( requestSettings, mockHandler, callbackContext, newMock );
519
520                 return newMock;
521         }
522
523         function completeJsonpCall( requestSettings, mockHandler, callbackContext, newMock ) {
524                 var json;
525
526                 // Successful response
527                 setTimeout(function() {
528                         jsonpSuccess( requestSettings, callbackContext, mockHandler );
529                         jsonpComplete( requestSettings, callbackContext );
530
531                         if ( newMock ) {
532                                 try {
533                                         json = $.parseJSON( mockHandler.responseText );
534                                 } catch (err) { /* just checking... */ }
535
536                                 newMock.resolveWith( callbackContext, [json || mockHandler.responseText] );
537                                 logger.log( mockHandler, ['JSONP mock call complete', mockHandler, newMock] );
538                         }
539                 }, parseResponseTimeOpt( mockHandler.responseTime ));
540         }
541
542
543         // Create the required JSONP callback function for the request
544         function createJsonpCallback( requestSettings, mockHandler, origSettings ) {
545                 var callbackContext = origSettings && origSettings.context || requestSettings;
546                 var jsonp = (typeof requestSettings.jsonpCallback === 'string' && requestSettings.jsonpCallback) || ('jsonp' + jsc++);
547
548                 // Replace the =? sequence both in the query string and the data
549                 if ( requestSettings.data ) {
550                         requestSettings.data = (requestSettings.data + '').replace(CALLBACK_REGEX, '=' + jsonp + '$1');
551                 }
552
553                 requestSettings.url = requestSettings.url.replace(CALLBACK_REGEX, '=' + jsonp + '$1');
554
555
556                 // Handle JSONP-style loading
557                 window[ jsonp ] = window[ jsonp ] || function() {
558                         jsonpSuccess( requestSettings, callbackContext, mockHandler );
559                         jsonpComplete( requestSettings, callbackContext );
560                         // Garbage collect
561                         window[ jsonp ] = undefined;
562
563                         try {
564                                 delete window[ jsonp ];
565                         } catch(e) {}
566                 };
567                 requestSettings.jsonpCallback = jsonp;
568         }
569
570         // The JSONP request was successful
571         function jsonpSuccess(requestSettings, callbackContext, mockHandler) {
572                 // If a local callback was specified, fire it and pass it the data
573                 if ( requestSettings.success ) {
574                         requestSettings.success.call( callbackContext, mockHandler.responseText || '', 'success', {} );
575                 }
576
577                 // Fire the global callback
578                 if ( requestSettings.global ) {
579                         (requestSettings.context ? $(requestSettings.context) : $.event).trigger('ajaxSuccess', [{}, requestSettings]);
580                 }
581         }
582
583         // The JSONP request was completed
584         function jsonpComplete(requestSettings, callbackContext) {
585                 if ( requestSettings.complete ) {
586                         requestSettings.complete.call( callbackContext, {
587                                 statusText: 'success',
588                                 status: 200
589                         } , 'success' );
590                 }
591
592                 // The request was completed
593                 if ( requestSettings.global ) {
594                         (requestSettings.context ? $(requestSettings.context) : $.event).trigger('ajaxComplete', [{}, requestSettings]);
595                 }
596
597                 // Handle the global AJAX counter
598                 if ( requestSettings.global && ! --$.active ) {
599                         $.event.trigger( 'ajaxStop' );
600                 }
601         }
602
603
604         // The core $.ajax replacement.
605         function handleAjax( url, origSettings ) {
606                 var mockRequest, requestSettings, mockHandler, overrideCallback;
607
608                 logger.debug( null, ['Ajax call intercepted', url, origSettings] );
609
610                 // If url is an object, simulate pre-1.5 signature
611                 if ( typeof url === 'object' ) {
612                         origSettings = url;
613                         url = undefined;
614                 } else {
615                         // work around to support 1.5 signature
616                         origSettings = origSettings || {};
617                         origSettings.url = url || origSettings.url;
618                 }
619
620                 // Extend the original settings for the request
621                 requestSettings = $.ajaxSetup({}, origSettings);
622                 requestSettings.type = requestSettings.method = requestSettings.method || requestSettings.type;
623
624                 // Generic function to override callback methods for use with
625                 // callback options (onAfterSuccess, onAfterError, onAfterComplete)
626                 overrideCallback = function(action, mockHandler) {
627                         var origHandler = origSettings[action.toLowerCase()];
628                         return function() {
629                                 if ( $.isFunction(origHandler) ) {
630                                         origHandler.apply(this, [].slice.call(arguments));
631                                 }
632                                 mockHandler['onAfter' + action]();
633                         };
634                 };
635
636                 // Iterate over our mock handlers (in registration order) until we find
637                 // one that is willing to intercept the request
638                 for(var k = 0; k < mockHandlers.length; k++) {
639                         if ( !mockHandlers[k] ) {
640                                 continue;
641                         }
642
643                         mockHandler = getMockForRequest( mockHandlers[k], requestSettings );
644                         if(!mockHandler) {
645                                 logger.debug( mockHandlers[k], ['Mock does not match request', url, requestSettings] );
646                                 // No valid mock found for this request
647                                 continue;
648                         }
649
650                         if ($.mockjaxSettings.retainAjaxCalls) {
651                                 mockedAjaxCalls.push(requestSettings);
652                         }
653
654                         // If logging is enabled, log the mock to the console
655                         logger.info( mockHandler, [
656                                 'MOCK ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url,
657                                 $.ajaxSetup({}, requestSettings)
658                         ] );
659
660
661                         if ( requestSettings.dataType && requestSettings.dataType.toUpperCase() === 'JSONP' ) {
662                                 if ((mockRequest = processJsonpMock( requestSettings, mockHandler, origSettings ))) {
663                                         // This mock will handle the JSONP request
664                                         return mockRequest;
665                                 }
666                         }
667
668                         // We are mocking, so there will be no cross domain request, however, jQuery
669                         // aggressively pursues this if the domains don't match, so we need to
670                         // explicitly disallow it. (See #136)
671                         origSettings.crossDomain = false;
672
673                         // Removed to fix #54 - keep the mocking data object intact
674                         //mockHandler.data = requestSettings.data;
675
676                         mockHandler.cache = requestSettings.cache;
677                         mockHandler.timeout = requestSettings.timeout;
678                         mockHandler.global = requestSettings.global;
679
680                         // In the case of a timeout, we just need to ensure
681                         // an actual jQuery timeout (That is, our reponse won't)
682                         // return faster than the timeout setting.
683                         if ( mockHandler.isTimeout ) {
684                                 if ( mockHandler.responseTime > 1 ) {
685                                         origSettings.timeout = mockHandler.responseTime - 1;
686                                 } else {
687                                         mockHandler.responseTime = 2;
688                                         origSettings.timeout = 1;
689                                 }
690                         }
691
692                         // Set up onAfter[X] callback functions
693                         if ( $.isFunction( mockHandler.onAfterSuccess ) ) {
694                                 origSettings.success = overrideCallback('Success', mockHandler);
695                         }
696                         if ( $.isFunction( mockHandler.onAfterError ) ) {
697                                 origSettings.error = overrideCallback('Error', mockHandler);
698                         }
699                         if ( $.isFunction( mockHandler.onAfterComplete ) ) {
700                                 origSettings.complete = overrideCallback('Complete', mockHandler);
701                         }
702
703                         copyUrlParameters(mockHandler, origSettings);
704
705                         /* jshint loopfunc:true */
706                         (function(mockHandler, requestSettings, origSettings, origHandler) {
707
708                                 mockRequest = _ajax.call($, $.extend(true, {}, origSettings, {
709                                         // Mock the XHR object
710                                         xhr: function() { return xhr( mockHandler, requestSettings, origSettings, origHandler ); }
711                                 }));
712                         })(mockHandler, requestSettings, origSettings, mockHandlers[k]);
713                         /* jshint loopfunc:false */
714
715                         return mockRequest;
716                 }
717
718                 // We don't have a mock request
719                 logger.log( null, ['No mock matched to request', url, origSettings] );
720                 if ($.mockjaxSettings.retainAjaxCalls) {
721                         unmockedAjaxCalls.push(origSettings);
722                 }
723                 if($.mockjaxSettings.throwUnmocked === true) {
724                         throw new Error('AJAX not mocked: ' + origSettings.url);
725                 }
726                 else { // trigger a normal request
727                         return _ajax.apply($, [origSettings]);
728                 }
729         }
730
731         /**
732         * Copies URL parameter values if they were captured by a regular expression
733         * @param {Object} mockHandler
734         * @param {Object} origSettings
735         */
736         function copyUrlParameters(mockHandler, origSettings) {
737                 //parameters aren't captured if the URL isn't a RegExp
738                 if (!(mockHandler.url instanceof RegExp)) {
739                         return;
740                 }
741                 //if no URL params were defined on the handler, don't attempt a capture
742                 if (!mockHandler.hasOwnProperty('urlParams')) {
743                         return;
744                 }
745                 var captures = mockHandler.url.exec(origSettings.url);
746                 //the whole RegExp match is always the first value in the capture results
747                 if (captures.length === 1) {
748                         return;
749                 }
750                 captures.shift();
751                 //use handler params as keys and capture resuts as values
752                 var i = 0,
753                 capturesLength = captures.length,
754                 paramsLength = mockHandler.urlParams.length,
755                 //in case the number of params specified is less than actual captures
756                 maxIterations = Math.min(capturesLength, paramsLength),
757                 paramValues = {};
758                 for (i; i < maxIterations; i++) {
759                         var key = mockHandler.urlParams[i];
760                         paramValues[key] = captures[i];
761                 }
762                 origSettings.urlParams = paramValues;
763         }
764
765         /**
766          * Clears handlers that mock given url
767          * @param url
768          * @returns {Array}
769          */
770         function clearByUrl(url) {
771                 var i, len,
772                         handler,
773                         results = [],
774                         match=url instanceof RegExp ?
775                                 function(testUrl) { return url.test(testUrl); } :
776                                 function(testUrl) { return url === testUrl; };
777                 for (i=0, len=mockHandlers.length; i<len; i++) {
778                         handler = mockHandlers[i];
779                         if (!match(handler.url)) {
780                                 results.push(handler);
781                         } else {
782                                 logger.log( handler, [
783                                         'Clearing mock: ' + (handler && handler.url),
784                                         handler
785                                 ] );
786                         }
787                 }
788                 return results;
789         }
790
791
792         // Public
793
794         $.extend({
795                 ajax: handleAjax
796         });
797
798         var logger = {
799                 _log: function logger( mockHandler, args, level ) {
800                         var loggerLevel = $.mockjaxSettings.logging;
801                         if (mockHandler && typeof mockHandler.logging !== 'undefined') {
802                                 loggerLevel = mockHandler.logging;
803                         }
804                         level = ( level === 0 ) ? level : ( level || logLevels.LOG );
805                         args = (args.splice) ? args : [ args ];
806
807                         // Is logging turned off for this mock or mockjax as a whole?
808                         // Or is this log message above the desired log level?
809                         if ( loggerLevel === false || loggerLevel < level ) {
810                                 return;
811                         }
812
813                         if ( $.mockjaxSettings.log ) {
814                                 return $.mockjaxSettings.log( mockHandler, args[1] || args[0] );
815                         } else if ( $.mockjaxSettings.logger && $.mockjaxSettings.logger[$.mockjaxSettings.logLevelMethods[level]] ) {
816                                 return $.mockjaxSettings.logger[$.mockjaxSettings.logLevelMethods[level]].apply( $.mockjaxSettings.logger, args );
817                         }
818                 },
819                 /**
820                  * Convenience method for logging a DEBUG level message
821                  * @param  {Object} m  The mock handler in question
822                  * @param  {Array|String|Object} a  The items to log
823                  * @return {?}  Will return whatever the $.mockjaxSettings.logger method for this level would return (generally 'undefined')
824                  */
825                 debug: function(m,a) { return logger._log(m,a,logLevels.DEBUG); },
826                 /**
827                  * @see logger.debug
828                  */
829                 log: function(m,a) { return logger._log(m,a,logLevels.LOG); },
830                 /**
831                  * @see logger.debug
832                  */
833                 info: function(m,a) { return logger._log(m,a,logLevels.INFO); },
834                 /**
835                  * @see logger.debug
836                  */
837                 warn: function(m,a) { return logger._log(m,a,logLevels.WARN); },
838                 /**
839                  * @see logger.debug
840                  */
841                 error: function(m,a) { return logger._log(m,a,logLevels.ERROR); }
842         };
843
844         var logLevels = {
845                 DEBUG: 4,
846                 LOG: 3,
847                 INFO: 2,
848                 WARN: 1,
849                 ERROR: 0
850         };
851
852         /**
853          * Default settings for mockjax. Some of these are used for defaults of
854          * individual mock handlers, and some are for the library as a whole.
855          * For individual mock handler settings, please see the README on the repo:
856          * https://github.com/jakerella/jquery-mockjax#api-methods
857          *
858          * @type {Object}
859          */
860         $.mockjaxSettings = {
861                 log:                            null, // this is only here for historical purposes... use $.mockjaxSettings.logger
862                 logger:                         window.console,
863                 logging:                        2,
864                 logLevelMethods:        ['error', 'warn', 'info', 'log', 'debug'],
865                 namespace:                      null,
866                 status:                         200,
867                 statusText:                     'OK',
868                 responseTime:           DEFAULT_RESPONSE_TIME,
869                 isTimeout:                      false,
870                 throwUnmocked:          false,
871                 retainAjaxCalls:        true,
872                 contentType:            'text/plain',
873                 response:                       '',
874                 responseText:           '',
875                 responseXML:            '',
876                 proxy:                          '',
877                 proxyType:                      'GET',
878
879                 lastModified:           null,
880                 etag:                           '',
881                 headers:                        {
882                                                                 etag: 'IJF@H#@923uf8023hFO@I#H#',
883                                                                 'content-type' : 'text/plain'
884                                                         }
885         };
886
887         /**
888          * Create a new mock Ajax handler. When a mock handler is matched during a
889          * $.ajax() call this library will intercept that request and fake a response
890          * using the data and methods in the mock. You can see all settings in the
891          * README of the main repository:
892          * https://github.com/jakerella/jquery-mockjax#api-methods
893          *
894          * @param  {Object} settings The mock handelr settings: https://github.com/jakerella/jquery-mockjax#api-methods
895          * @return {Number}               The id (index) of the mock handler suitable for clearing (see $.mockjax.clear())
896          */
897         $.mockjax = function(settings) {
898                 // Multiple mocks.
899                 if ( $.isArray(settings) ) {
900                         return $.map(settings, function(s) {
901                                 return $.mockjax(s);
902                         });
903                 }
904
905                 var i = mockHandlers.length;
906                 mockHandlers[i] = settings;
907                 logger.log( settings, ['Created new mock handler', settings] );
908                 return i;
909         };
910
911         $.mockjax._logger = logger;
912
913         /**
914          * Remove an Ajax mock from those held in memory. This will prevent any
915          * future Ajax request mocking for matched requests.
916          * NOTE: Clearing a mock will not prevent the resolution of in progress requests
917          *
918          * @param  {Number|String|RegExp} i  OPTIONAL The mock to clear. If not provided, all mocks are cleared,
919          *                                   if a number it is the index in the in-memory cache. If a string or
920          *                                   RegExp, find a mock that matches that URL and clear it.
921          * @return {void}
922          */
923         $.mockjax.clear = function(i) {
924                 if ( typeof i === 'string' || i instanceof RegExp) {
925                         mockHandlers = clearByUrl(i);
926                 } else if ( i || i === 0 ) {
927                         logger.log( mockHandlers[i], [
928                                 'Clearing mock: ' + (mockHandlers[i] && mockHandlers[i].url),
929                                 mockHandlers[i]
930                         ] );
931                         mockHandlers[i] = null;
932                 } else {
933                         logger.log( null, 'Clearing all mocks' );
934                         mockHandlers = [];
935                 }
936                 mockedAjaxCalls = [];
937                 unmockedAjaxCalls = [];
938         };
939
940         /**
941          * By default all Ajax requests performed after loading Mockjax are recorded
942          * so that we can see which requests were mocked and which were not. This
943          * method allows the developer to clear those retained requests.
944          *
945          * @return {void}
946          */
947         $.mockjax.clearRetainedAjaxCalls = function() {
948                 mockedAjaxCalls = [];
949                 unmockedAjaxCalls = [];
950                 logger.debug( null, 'Cleared retained ajax calls' );
951         };
952
953         /**
954          * Retrive the mock handler with the given id (index).
955          *
956          * @param  {Number} i  The id (index) to retrieve
957          * @return {Object}     The mock handler settings
958          */
959         $.mockjax.handler = function(i) {
960                 if ( arguments.length === 1 ) {
961                         return mockHandlers[i];
962                 }
963         };
964
965         /**
966          * Retrieve all Ajax calls that have been mocked by this library during the
967          * current session (in other words, only since you last loaded this file).
968          *
969          * @return {Array}  The mocked Ajax calls (request settings)
970          */
971         $.mockjax.mockedAjaxCalls = function() {
972                 return mockedAjaxCalls;
973         };
974
975         /**
976          * Return all mock handlers that have NOT been matched against Ajax requests
977          *
978          * @return {Array}  The mock handlers
979          */
980         $.mockjax.unfiredHandlers = function() {
981                 var results = [];
982                 for (var i=0, len=mockHandlers.length; i<len; i++) {
983                         var handler = mockHandlers[i];
984                         if (handler !== null && !handler.fired) {
985                                 results.push(handler);
986                         }
987                 }
988                 return results;
989         };
990
991         /**
992          * Retrieve all Ajax calls that have NOT been mocked by this library during
993          * the current session (in other words, only since you last loaded this file).
994          *
995          * @return {Array}  The mocked Ajax calls (request settings)
996          */
997         $.mockjax.unmockedAjaxCalls = function() {
998                 return unmockedAjaxCalls;
999         };
1000
1001         return $.mockjax;
1002
1003 }));