2 * MockJax - jQuery Plugin to Mock Ajax requests
6 * Source: http://github.com/appendto/jquery-mockjax
7 * Docs: http://enterprisejquery.com/2010/07/mock-your-ajax-requests-with-mockjax-for-rapid-development
9 * Author: Jonathan Sharp (http://jdsharp.com)
12 * Copyright (c) 2010 appendTo LLC.
13 * Dual licensed under the MIT or GPL licenses.
14 * http://appendto.com/open-source-licenses
20 function parseXML(xml
) {
21 if ( window
['DOMParser'] == undefined && window
.ActiveXObject
) {
22 DOMParser = function() { };
23 DOMParser
.prototype.parseFromString = function( xmlString
) {
24 var doc
= new ActiveXObject('Microsoft.XMLDOM');
26 doc
.loadXML( xmlString
);
32 var xmlDoc
= ( new DOMParser() ).parseFromString( xml
, 'text/xml' );
33 if ( $.isXMLDoc( xmlDoc
) ) {
34 var err
= $('parsererror', xmlDoc
);
35 if ( err
.length
== 1 ) {
36 throw('Error: ' + $(xmlDoc
).text() );
39 throw('Unable to parse XML');
42 var msg
= ( e
.name
== undefined ? e
: e
.name
+ ': ' + e
.message
);
43 $(document
).trigger('xmlParseError', [ msg
]);
50 ajax: function(origSettings
) {
51 var s
= jQuery
.extend(true, {}, jQuery
.ajaxSettings
, origSettings
),
53 // Iterate over our mock handlers (in registration order) until we find
54 // one that is willing to intercept the request
55 $.each(mockHandlers
, function(k
, v
) {
56 if ( !mockHandlers
[k
] ) {
60 // If the mock was registered with a function, let the function decide if we
61 // want to mock this request
62 if ( $.isFunction(mockHandlers
[k
]) ) {
63 m
= mockHandlers
[k
](s
);
66 // Inspect the URL of the request and check if the mock handler's url
67 // matches the url for this ajax request
68 if ( $.isFunction(m
.url
.test
) ) {
69 // The user provided a regex for the url, test it
70 if ( !m
.url
.test( s
.url
) ) {
74 // Look for a simple wildcard '*' or a direct URL match
75 var star
= m
.url
.indexOf('*');
76 if ( ( m
.url
!= '*' && m
.url
!= s
.url
&& star
== -1 ) ||
77 ( star
> -1 && m
.url
.substr(0, star
) != s
.url
.substr(0, star
) ) ) {
78 // The url we tested did not match the wildcard *
83 // Inspect the data submitted in the request (either POST body or GET query string)
84 if ( m
.data
&& s
.data
) {
85 var identical
= false;
86 // Deep inspect the identity of the objects
87 (function ident(mock
, live
) {
88 // Test for situations where the data is a querystring (not an object)
89 if (typeof live
=== 'string') {
90 // Querystring may be a regex
91 identical
= $.isFunction( mock
.test
) ? mock
.test(live
) : mock
== live
;
94 $.each(mock
, function(k
, v
) {
95 if ( live
[k
] === undefined ) {
100 if ( typeof live
[k
] == 'object' ) {
101 return ident(mock
[k
], live
[k
]);
103 if ( $.isFunction( mock
[k
].test
) ) {
104 identical
= mock
[k
].test(live
[k
]);
106 identical
= ( mock
[k
] == live
[k
] );
113 // They're not identical, do not mock this request
114 if ( identical
== false ) {
118 // Inspect the request type
119 if ( m
&& m
.type
&& m
.type
!= s
.type
) {
120 // The request type doesn't match (GET vs. POST)
128 // Handle console logging
129 var c
= $.extend({}, $.mockjaxSettings
, m
);
130 if ( c
.log
&& $.isFunction(c
.log
) ) {
131 c
.log('MOCK ' + s
.type
.toUpperCase() + ': ' + s
.url
, $.extend({}, s
));
134 var jsre
= /=\?(&|$)/, jsc
= (new Date()).getTime();
136 // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
137 // because there isn't an easy hook for the cross domain script tag of jsonp
138 if ( s
.dataType
=== "jsonp" ) {
139 if ( s
.type
.toUpperCase() === "GET" ) {
140 if ( !jsre
.test( s
.url
) ) {
141 s
.url
+= (rquery
.test( s
.url
) ? "&" : "?") + (s
.jsonp
|| "callback") + "=?";
143 } else if ( !s
.data
|| !jsre
.test(s
.data
) ) {
144 s
.data
= (s
.data
? s
.data
+ "&" : "") + (s
.jsonp
|| "callback") + "=?";
149 // Build temporary JSONP function
150 if ( s
.dataType
=== "json" && (s
.data
&& jsre
.test(s
.data
) || jsre
.test(s
.url
)) ) {
151 jsonp
= s
.jsonpCallback
|| ("jsonp" + jsc
++);
153 // Replace the =? sequence both in the query string and the data
155 s
.data
= (s
.data
+ "").replace(jsre
, "=" + jsonp
+ "$1");
158 s
.url
= s
.url
.replace(jsre
, "=" + jsonp
+ "$1");
160 // We need to make sure
161 // that a JSONP style response is executed properly
162 s
.dataType
= "script";
164 // Handle JSONP-style loading
165 window
[ jsonp
] = window
[ jsonp
] || function( tmp
) {
170 window
[ jsonp
] = undefined;
173 delete window
[ jsonp
];
177 head
.removeChild( script
);
182 var rurl
= /^(\w+:)?\/\/([^\/?#]+)/,
183 parts
= rurl
.exec( s
.url
),
184 remote
= parts
&& (parts
[1] && parts
[1] !== location
.protocol
|| parts
[2] !== location
.host
);
186 // Test if we are going to create a script tag (if so, intercept & mock)
187 if ( s
.dataType
=== "script" && s
.type
.toUpperCase() === "GET" && remote
) {
188 // Synthesize the mock request for adding a script tag
189 var callbackContext
= origSettings
&& origSettings
.context
|| s
;
192 // If a local callback was specified, fire it and pass it the data
194 s
.success
.call( callbackContext
, ( m
.response
? m
.response
.toString() : m
.responseText
|| ''), status
, {} );
197 // Fire the global callback
199 trigger( "ajaxSuccess", [{}, s
] );
203 function complete() {
206 s
.complete
.call( callbackContext
, {} , status
);
209 // The request was completed
211 trigger( "ajaxComplete", [{}, s
] );
214 // Handle the global AJAX counter
215 if ( s
.global
&& ! --jQuery
.active
) {
216 jQuery
.event
.trigger( "ajaxStop" );
220 function trigger(type
, args
) {
221 (s
.context
? jQuery(s
.context
) : jQuery
.event
).trigger(type
, args
);
224 if ( m
.response
&& $.isFunction(m
.response
) ) {
225 m
.response(origSettings
);
227 $.globalEval(m
.responseText
);
233 mock
= _ajax
.call($, $.extend(true, {}, origSettings
, {
234 // Mock the XHR object
236 // Extend with our default mockjax settings
237 m
= $.extend({}, $.mockjaxSettings
, m
);
239 if ( m
.contentType
) {
240 m
.headers
['content-type'] = m
.contentType
;
243 // Return our mock xhr object
247 open: function() { },
249 // This is a substitute for < 1.4 which lacks $.proxy
250 var process
= (function(that
) {
253 // The request has returned
254 this.status
= m
.status
;
257 // We have an executable function, call it to give
258 // the mock handler a chance to update it's data
259 if ( $.isFunction(m
.response
) ) {
260 m
.response(origSettings
);
262 // Copy over our mock to our xhr object before passing control back to
263 // jQuery's onreadystatechange callback
264 if ( s
.dataType
== 'json' && ( typeof m
.responseText
== 'object' ) ) {
265 this.responseText
= JSON
.stringify(m
.responseText
);
266 } else if ( s
.dataType
== 'xml' ) {
267 if ( typeof m
.responseXML
== 'string' ) {
268 this.responseXML
= parseXML(m
.responseXML
);
270 this.responseXML
= m
.responseXML
;
273 this.responseText
= m
.responseText
;
275 // jQuery < 1.4 doesn't have onreadystate change for xhr
276 if ( $.isFunction(this.onreadystatechange
) ) {
277 this.onreadystatechange( m
.isTimeout
? 'timeout' : undefined );
284 // We're proxying this request and loading in an external file instead
290 dataType
: s
.dataType
,
291 complete: function(xhr
, txt
) {
292 m
.responseXML
= xhr
.responseXML
;
293 m
.responseText
= xhr
.responseText
;
294 this.responseTimer
= setTimeout(process
, m
.responseTime
|| 0);
298 // type == 'POST' || 'GET' || 'DELETE'
299 if ( s
.async
=== false ) {
300 // TODO: Blocking delay
303 this.responseTimer
= setTimeout(process
, m
.responseTime
|| 50);
308 clearTimeout(this.responseTimer
);
310 setRequestHeader: function() { },
311 getResponseHeader: function(header
) {
312 // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
313 if ( m
.headers
&& m
.headers
[header
] ) {
314 // Return arbitrary headers
315 return m
.headers
[header
];
316 } else if ( header
.toLowerCase() == 'last-modified' ) {
317 return m
.lastModified
|| (new Date()).toString();
318 } else if ( header
.toLowerCase() == 'etag' ) {
320 } else if ( header
.toLowerCase() == 'content-type' ) {
321 return m
.contentType
|| 'text/plain';
324 getAllResponseHeaders: function() {
326 $.each(m
.headers
, function(k
, v
) {
327 headers
+= k
+ ': ' + v
+ "\n";
337 // We don't have a mock request, trigger a normal request
339 return _ajax
.apply($, arguments
);
346 $.mockjaxSettings
= {
350 window
['console'] && window
.console
.log
&& window
.console
.log(msg
);
355 contentType
: 'text/plain',
365 etag
: 'IJF@H#@923uf8023hFO@I#H#',
366 'content-type' : 'text/plain'
370 $.mockjax = function(settings
) {
371 var i
= mockHandlers
.length
;
372 mockHandlers
[i
] = settings
;
375 $.mockjaxClear = function(i
) {
376 if ( arguments
.length
== 1 ) {
377 mockHandlers
[i
] = null;