Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
776 lars 1
/*!
2
 * MockJax - jQuery Plugin to Mock Ajax requests
3
 *
4
 * Version:  1.5.0pre
5
 * Released:
6
 * Home:   http://github.com/appendto/jquery-mockjax
7
 * Author:   Jonathan Sharp (http://jdsharp.com)
8
 * License:  MIT,GPL
9
 *
10
 * Copyright (c) 2011 appendTo LLC.
11
 * Dual licensed under the MIT or GPL licenses.
12
 * http://appendto.com/open-source-licenses
13
 */
14
(function($) {
15
	var _ajax = $.ajax,
16
		mockHandlers = [],
17
		CALLBACK_REGEX = /=\?(&|$)/,
18
		jsc = (new Date()).getTime();
19
 
20
 
21
	// Parse the given XML string.
22
	function parseXML(xml) {
23
		if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
24
			DOMParser = function() { };
25
			DOMParser.prototype.parseFromString = function( xmlString ) {
26
				var doc = new ActiveXObject('Microsoft.XMLDOM');
27
				doc.async = 'false';
28
				doc.loadXML( xmlString );
29
				return doc;
30
			};
31
		}
32
 
33
		try {
34
			var xmlDoc 	= ( new DOMParser() ).parseFromString( xml, 'text/xml' );
35
			if ( $.isXMLDoc( xmlDoc ) ) {
36
				var err = $('parsererror', xmlDoc);
37
				if ( err.length == 1 ) {
38
					throw('Error: ' + $(xmlDoc).text() );
39
				}
40
			} else {
41
				throw('Unable to parse XML');
42
			}
43
		} catch( e ) {
44
			var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
45
			$(document).trigger('xmlParseError', [ msg ]);
46
			return undefined;
47
		}
48
		return xmlDoc;
49
	}
50
 
51
	// Trigger a jQuery event
52
	function trigger(s, type, args) {
53
		(s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
54
	}
55
 
56
	// Check if the data field on the mock handler and the request match. This
57
	// can be used to restrict a mock handler to being used only when a certain
58
	// set of data is passed to it.
59
	function isMockDataEqual( mock, live ) {
60
		var identical = false;
61
		// Test for situations where the data is a querystring (not an object)
62
		if (typeof live === 'string') {
63
			// Querystring may be a regex
64
			return $.isFunction( mock.test ) ? mock.test(live) : mock == live;
65
		}
66
		$.each(mock, function(k, v) {
67
			if ( live[k] === undefined ) {
68
				identical = false;
69
				return identical;
70
			} else {
71
				identical = true;
72
				if ( typeof live[k] == 'object' ) {
73
					return isMockDataEqual(mock[k], live[k]);
74
				} else {
75
					if ( $.isFunction( mock[k].test ) ) {
76
						identical = mock[k].test(live[k]);
77
					} else {
78
						identical = ( mock[k] == live[k] );
79
					}
80
					return identical;
81
				}
82
			}
83
		});
84
 
85
		return identical;
86
	}
87
 
88
	// Check the given handler should mock the given request
89
	function getMockForRequest( handler, requestSettings ) {
90
		// If the mock was registered with a function, let the function decide if we
91
		// want to mock this request
92
		if ( $.isFunction(handler) ) {
93
			return handler( requestSettings );
94
		}
95
 
96
		// Inspect the URL of the request and check if the mock handler's url
97
		// matches the url for this ajax request
98
		if ( $.isFunction(handler.url.test) ) {
99
			// The user provided a regex for the url, test it
100
			if ( !handler.url.test( requestSettings.url ) ) {
101
				return null;
102
			}
103
		} else {
104
			// Look for a simple wildcard '*' or a direct URL match
105
			var star = handler.url.indexOf('*');
106
			if (handler.url !== requestSettings.url && star === -1 ||
107
					!new RegExp(handler.url.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&").replace('*', '.+')).test(requestSettings.url)) {
108
				return null;
109
			}
110
		}
111
 
112
		// Inspect the data submitted in the request (either POST body or GET query string)
113
		if ( handler.data && requestSettings.data ) {
114
			if ( !isMockDataEqual(handler.data, requestSettings.data) ) {
115
				// They're not identical, do not mock this request
116
				return null;
117
			}
118
		}
119
		// Inspect the request type
120
		if ( handler && handler.type &&
121
				 handler.type.toLowerCase() != requestSettings.type.toLowerCase() ) {
122
			// The request type doesn't match (GET vs. POST)
123
			return null;
124
		}
125
 
126
		return handler;
127
	}
128
 
129
	// If logging is enabled, log the mock to the console
130
	function logMock( mockHandler, requestSettings ) {
131
		var c = $.extend({}, $.mockjaxSettings, mockHandler);
132
		if ( c.log && $.isFunction(c.log) ) {
133
			c.log('MOCK ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url, $.extend({}, requestSettings));
134
		}
135
	}
136
 
137
	// Process the xhr objects send operation
138
	function _xhrSend(mockHandler, requestSettings, origSettings) {
139
 
140
		// This is a substitute for < 1.4 which lacks $.proxy
141
		var process = (function(that) {
142
			return function() {
143
				return (function() {
144
					// The request has returned
145
					this.status 		= mockHandler.status;
146
					this.statusText		= mockHandler.statusText;
147
					this.readyState 	= 4;
148
 
149
					// We have an executable function, call it to give
150
					// the mock handler a chance to update it's data
151
					if ( $.isFunction(mockHandler.response) ) {
152
						mockHandler.response(origSettings);
153
					}
154
					// Copy over our mock to our xhr object before passing control back to
155
					// jQuery's onreadystatechange callback
156
					if ( requestSettings.dataType == 'json' && ( typeof mockHandler.responseText == 'object' ) ) {
157
						this.responseText = JSON.stringify(mockHandler.responseText);
158
					} else if ( requestSettings.dataType == 'xml' ) {
159
						if ( typeof mockHandler.responseXML == 'string' ) {
160
							this.responseXML = parseXML(mockHandler.responseXML);
161
						} else {
162
							this.responseXML = mockHandler.responseXML;
163
						}
164
					} else {
165
						this.responseText = mockHandler.responseText;
166
					}
167
					if( typeof mockHandler.status == 'number' || typeof mockHandler.status == 'string' ) {
168
						this.status = mockHandler.status;
169
					}
170
					if( typeof mockHandler.statusText === "string") {
171
						this.statusText = mockHandler.statusText;
172
					}
173
					// jQuery < 1.4 doesn't have onreadystate change for xhr
174
					if ( $.isFunction(this.onreadystatechange) ) {
175
						if( mockHandler.isTimeout) {
176
							this.status = -1;
177
						}
178
						this.onreadystatechange( mockHandler.isTimeout ? 'timeout' : undefined );
179
					} else if ( mockHandler.isTimeout ) {
180
						// Fix for 1.3.2 timeout to keep success from firing.
181
						this.status = -1;
182
					}
183
				}).apply(that);
184
			};
185
		})(this);
186
 
187
		if ( mockHandler.proxy ) {
188
			// We're proxying this request and loading in an external file instead
189
			_ajax({
190
				global: false,
191
				url: mockHandler.proxy,
192
				type: mockHandler.proxyType,
193
				data: mockHandler.data,
194
				dataType: requestSettings.dataType === "script" ? "text/plain" : requestSettings.dataType,
195
				complete: function(xhr, txt) {
196
					mockHandler.responseXML = xhr.responseXML;
197
					mockHandler.responseText = xhr.responseText;
198
					mockHandler.status = xhr.status;
199
					mockHandler.statusText = xhr.statusText;
200
					this.responseTimer = setTimeout(process, mockHandler.responseTime || 0);
201
				}
202
			});
203
		} else {
204
			// type == 'POST' || 'GET' || 'DELETE'
205
			if ( requestSettings.async === false ) {
206
				// TODO: Blocking delay
207
				process();
208
			} else {
209
				this.responseTimer = setTimeout(process, mockHandler.responseTime || 50);
210
			}
211
		}
212
	}
213
 
214
	// Construct a mocked XHR Object
215
	function xhr(mockHandler, requestSettings, origSettings, origHandler) {
216
		// Extend with our default mockjax settings
217
		mockHandler = $.extend({}, $.mockjaxSettings, mockHandler);
218
 
219
		if (typeof mockHandler.headers === 'undefined') {
220
			mockHandler.headers = {};
221
		}
222
		if ( mockHandler.contentType ) {
223
			mockHandler.headers['content-type'] = mockHandler.contentType;
224
		}
225
 
226
		return {
227
			status: mockHandler.status,
228
			statusText: mockHandler.statusText,
229
			readyState: 1,
230
			open: function() { },
231
			send: function() {
232
				origHandler.fired = true;
233
				_xhrSend.call(this, mockHandler, requestSettings, origSettings);
234
			},
235
			abort: function() {
236
				clearTimeout(this.responseTimer);
237
			},
238
			setRequestHeader: function(header, value) {
239
				mockHandler.headers[header] = value;
240
			},
241
			getResponseHeader: function(header) {
242
				// 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
243
				if ( mockHandler.headers && mockHandler.headers[header] ) {
244
					// Return arbitrary headers
245
					return mockHandler.headers[header];
246
				} else if ( header.toLowerCase() == 'last-modified' ) {
247
					return mockHandler.lastModified || (new Date()).toString();
248
				} else if ( header.toLowerCase() == 'etag' ) {
249
					return mockHandler.etag || '';
250
				} else if ( header.toLowerCase() == 'content-type' ) {
251
					return mockHandler.contentType || 'text/plain';
252
				}
253
			},
254
			getAllResponseHeaders: function() {
255
				var headers = '';
256
				$.each(mockHandler.headers, function(k, v) {
257
					headers += k + ': ' + v + "\n";
258
				});
259
				return headers;
260
			}
261
		};
262
	}
263
 
264
	// Process a JSONP mock request.
265
	function processJsonpMock( requestSettings, mockHandler, origSettings ) {
266
		// Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
267
		// because there isn't an easy hook for the cross domain script tag of jsonp
268
 
269
		processJsonpUrl( requestSettings );
270
 
271
		requestSettings.dataType = "json";
272
		if(requestSettings.data && CALLBACK_REGEX.test(requestSettings.data) || CALLBACK_REGEX.test(requestSettings.url)) {
273
			createJsonpCallback(requestSettings, mockHandler);
274
 
275
			// We need to make sure
276
			// that a JSONP style response is executed properly
277
 
278
			var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
279
				parts = rurl.exec( requestSettings.url ),
280
				remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
281
 
282
			requestSettings.dataType = "script";
283
			if(requestSettings.type.toUpperCase() === "GET" && remote ) {
284
				var newMockReturn = processJsonpRequest( requestSettings, mockHandler, origSettings );
285
 
286
				// Check if we are supposed to return a Deferred back to the mock call, or just
287
				// signal success
288
				if(newMockReturn) {
289
					return newMockReturn;
290
				} else {
291
					return true;
292
				}
293
			}
294
		}
295
		return null;
296
	}
297
 
298
	// Append the required callback parameter to the end of the request URL, for a JSONP request
299
	function processJsonpUrl( requestSettings ) {
300
		if ( requestSettings.type.toUpperCase() === "GET" ) {
301
			if ( !CALLBACK_REGEX.test( requestSettings.url ) ) {
302
				requestSettings.url += (/\?/.test( requestSettings.url ) ? "&" : "?") +
303
					(requestSettings.jsonp || "callback") + "=?";
304
			}
305
		} else if ( !requestSettings.data || !CALLBACK_REGEX.test(requestSettings.data) ) {
306
			requestSettings.data = (requestSettings.data ? requestSettings.data + "&" : "") + (requestSettings.jsonp || "callback") + "=?";
307
		}
308
	}
309
 
310
	// Process a JSONP request by evaluating the mocked response text
311
	function processJsonpRequest( requestSettings, mockHandler, origSettings ) {
312
		// Synthesize the mock request for adding a script tag
313
		var callbackContext = origSettings && origSettings.context || requestSettings,
314
			newMock = null;
315
 
316
 
317
		// If the response handler on the moock is a function, call it
318
		if ( mockHandler.response && $.isFunction(mockHandler.response) ) {
319
			mockHandler.response(origSettings);
320
		} else {
321
 
322
			// Evaluate the responseText javascript in a global context
323
			if( typeof mockHandler.responseText === 'object' ) {
324
				$.globalEval( '(' + JSON.stringify( mockHandler.responseText ) + ')');
325
			} else {
326
				$.globalEval( '(' + mockHandler.responseText + ')');
327
			}
328
		}
329
 
330
		// Successful response
331
		jsonpSuccess( requestSettings, mockHandler );
332
		jsonpComplete( requestSettings, mockHandler );
333
 
334
		// If we are running under jQuery 1.5+, return a deferred object
335
		if(jQuery.Deferred){
336
			newMock = new jQuery.Deferred();
337
			if(typeof mockHandler.responseText == "object"){
338
				newMock.resolve( mockHandler.responseText );
339
			}
340
			else{
341
				newMock.resolve( jQuery.parseJSON( mockHandler.responseText ) );
342
			}
343
		}
344
		return newMock;
345
	}
346
 
347
 
348
	// Create the required JSONP callback function for the request
349
	function createJsonpCallback( requestSettings, mockHandler ) {
350
		jsonp = requestSettings.jsonpCallback || ("jsonp" + jsc++);
351
 
352
		// Replace the =? sequence both in the query string and the data
353
		if ( requestSettings.data ) {
354
			requestSettings.data = (requestSettings.data + "").replace(CALLBACK_REGEX, "=" + jsonp + "$1");
355
		}
356
 
357
		requestSettings.url = requestSettings.url.replace(CALLBACK_REGEX, "=" + jsonp + "$1");
358
 
359
 
360
		// Handle JSONP-style loading
361
		window[ jsonp ] = window[ jsonp ] || function( tmp ) {
362
			data = tmp;
363
			jsonpSuccess( requestSettings, mockHandler );
364
			jsonpComplete( requestSettings, mockHandler );
365
			// Garbage collect
366
			window[ jsonp ] = undefined;
367
 
368
			try {
369
				delete window[ jsonp ];
370
			} catch(e) {}
371
 
372
			if ( head ) {
373
				head.removeChild( script );
374
			}
375
		};
376
	}
377
 
378
	// The JSONP request was successful
379
	function jsonpSuccess(requestSettings, mockHandler) {
380
		// If a local callback was specified, fire it and pass it the data
381
		if ( requestSettings.success ) {
382
			requestSettings.success.call( callbackContext, ( mockHandler.response ? mockHandler.response.toString() : mockHandler.responseText || ''), status, {} );
383
		}
384
 
385
		// Fire the global callback
386
		if ( requestSettings.global ) {
387
			trigger(requestSettings, "ajaxSuccess", [{}, requestSettings] );
388
		}
389
	}
390
 
391
	// The JSONP request was completed
392
	function jsonpComplete(requestSettings, mockHandler) {
393
		// Process result
394
		if ( requestSettings.complete ) {
395
			requestSettings.complete.call( callbackContext, {} , status );
396
		}
397
 
398
		// The request was completed
399
		if ( requestSettings.global ) {
400
			trigger( "ajaxComplete", [{}, requestSettings] );
401
		}
402
 
403
		// Handle the global AJAX counter
404
		if ( requestSettings.global && ! --jQuery.active ) {
405
			jQuery.event.trigger( "ajaxStop" );
406
		}
407
	}
408
 
409
 
410
	// The core $.ajax replacement.
411
	function handleAjax( url, origSettings ) {
412
		var mockRequest, requestSettings, mockHandler;
413
 
414
		// If url is an object, simulate pre-1.5 signature
415
		if ( typeof url === "object" ) {
416
			origSettings = url;
417
			url = undefined;
418
		} else {
419
			// work around to support 1.5 signature
420
			origSettings.url = url;
421
		}
422
 
423
		// Extend the original settings for the request
424
		requestSettings = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings);
425
 
426
		// Iterate over our mock handlers (in registration order) until we find
427
		// one that is willing to intercept the request
428
		for(var k = 0; k < mockHandlers.length; k++) {
429
			if ( !mockHandlers[k] ) {
430
				continue;
431
			}
432
 
433
			mockHandler = getMockForRequest( mockHandlers[k], requestSettings );
434
			if(!mockHandler) {
435
				// No valid mock found for this request
436
				continue;
437
			}
438
 
439
			// Handle console logging
440
			logMock( mockHandler, requestSettings );
441
 
442
 
443
			if ( requestSettings.dataType === "jsonp" ) {
444
				if ((mockRequest = processJsonpMock( requestSettings, mockHandler, origSettings ))) {
445
					// This mock will handle the JSONP request
446
					return mockRequest;
447
				}
448
			}
449
 
450
 
451
			// Removed to fix #54 - keep the mocking data object intact
452
			//mockHandler.data = requestSettings.data;
453
 
454
			mockHandler.cache = requestSettings.cache;
455
			mockHandler.timeout = requestSettings.timeout;
456
			mockHandler.global = requestSettings.global;
457
 
458
			(function(mockHandler, requestSettings, origSettings, origHandler) {
459
				mockRequest = _ajax.call($, $.extend(true, {}, origSettings, {
460
					// Mock the XHR object
461
					xhr: function() { return xhr( mockHandler, requestSettings, origSettings, origHandler ) }
462
				}));
463
			})(mockHandler, requestSettings, origSettings, mockHandlers[k]);
464
 
465
			return mockRequest;
466
		}
467
 
468
		// We don't have a mock request, trigger a normal request
469
		return _ajax.apply($, [origSettings]);
470
	}
471
 
472
 
473
	// Public
474
 
475
	$.extend({
476
		ajax: handleAjax
477
	});
478
 
479
	$.mockjaxSettings = {
480
		//url:        null,
481
		//type:       'GET',
482
		log:          function(msg) {
483
										window['console'] && window.console.log && window.console.log(msg);
484
					  			},
485
		status:       200,
486
		statusText:   "OK",
487
		responseTime: 500,
488
		isTimeout:    false,
489
		contentType:  'text/plain',
490
		response:     '',
491
		responseText: '',
492
		responseXML:  '',
493
		proxy:        '',
494
		proxyType:    'GET',
495
 
496
		lastModified: null,
497
		etag:         '',
498
		headers: {
499
			etag: 'IJF@H#@923uf8023hFO@I#H#',
500
			'content-type' : 'text/plain'
501
		}
502
	};
503
 
504
	$.mockjax = function(settings) {
505
		var i = mockHandlers.length;
506
		mockHandlers[i] = settings;
507
		return i;
508
	};
509
	$.mockjaxClear = function(i) {
510
		if ( arguments.length == 1 ) {
511
			mockHandlers[i] = null;
512
		} else {
513
			mockHandlers = [];
514
		}
515
	};
516
	$.mockjax.handler = function(i) {
517
	  if ( arguments.length == 1 ) {
518
			return mockHandlers[i];
519
		}
520
	};
521
})(jQuery);