PageRenderTime 106ms CodeModel.GetById 33ms app.highlight 63ms RepoModel.GetById 1ms app.codeStats 0ms

/wwwroot/vendor/jack.js

http://github.com/AF83/ucengine
JavaScript | 903 lines | 798 code | 74 blank | 31 comment | 213 complexity | fe057367a28f77908e83035c7a85cfa8 MD5 | raw file
  1/**
  2 *
  3 *  JACK :: JavaScript Mocking.
  4 *  Version: $Id$
  5 *
  6 */
  7
  8
  9function jack() {} // This needs to be here to make error reporting work correctly in IE.
 10
 11(function (){ // START HIDING FROM GLOBAL SCOPE
 12	/** EXPORT JACK **/
 13	window.jack = new Jack();
 14	window.jack.matchers = new Matchers();
 15	window.jack.util = new Util();
 16	window.jack.FunctionSpecification = FunctionSpecification;
 17	window.jack.FunctionGrab = FunctionGrab;
 18	return;
 19
 20
 21	/**
 22	 * Constructor for object that will be exposed as the global jack
 23	 */
 24	function Jack() {
 25		var functionGrabs = {};
 26		var objectGrabs = {};
 27		var environment = new Environment();
 28		var reportMessages = [];
 29		var currentExpectation = null;
 30		var publicApi = createPublicApi();
 31		return publicApi;
 32
 33		function createPublicApi() {
 34			var api = jackFunction;
 35			api.grab = grab;
 36			api.create = create;
 37			api.inspect = inspect;
 38			api.expect = expect;
 39			api.verify = verify;
 40			api.report = report;
 41			api.reportAll = reportAll;
 42			api.env = environment;
 43			return api;
 44		}
 45		function jackFunction(delegate) {
 46			before();
 47			firstPass(delegate);
 48			// secondPass(delegate);
 49			after();
 50		}
 51		function before() {
 52			functionGrabs = {};
 53			objectGrabs = {};
 54			environment.reset();
 55		}
 56		function firstPass(delegate) {
 57			delegate();
 58		}
 59		function secondPass(delegate) {
 60			var oldExpect = publicApi.expect;
 61			publicApi.expect = function(name) {
 62				var fakeEx = {};
 63				var grab = findGrab(name);
 64				if(grab._beenThroughSecondPass) {
 65					var ex = grab.expect();
 66					for(prop in ex) {
 67						if(typeof ex[prop] == "function") {
 68							fakeEx[prop] = function() { return fakeEx; }
 69						}
 70					}
 71				}
 72				grab._beenThroughSecondPass = true;
 73				return fakeEx;
 74			};
 75			var findMore = true;
 76			for(var i=0; findMore && i<10; i++) {
 77				try {
 78					delegate();
 79					findMore = false;
 80				} catch(exception) {
 81					var line = -1;
 82					if(exception.lineNumber != null) {
 83						line = exception.lineNumber;
 84					} else if(exception['opera#sourceloc'] != null) {
 85						line = exception['opera#sourceloc'];
 86					}
 87					currentExpectation._lineNumber = line;
 88				}
 89			}
 90			publicApi.expect = oldExpect;
 91		}
 92		function after() {
 93			var reports = getTextReports();
 94			resetGrabs();
 95			if(reports.length > 0) {
 96				environment.report(reports[0]);
 97			}
 98		}
 99		function getTextReports() {
100			var failedReports = [];
101			for(var name in functionGrabs) {
102				var reports = functionGrabs[name].reportAll(name);
103				for(var i=0; i<reports.length; i++) {
104					if(reports[i].fail) {
105						failedReports.push(reports[i].message);
106					}
107				}
108			}
109			for(var name in objectGrabs) {
110				var reports = objectGrabs[name].report(name);
111				for(var i=0; i<reports.length; i++) {
112					if(reports[i].fail) {
113						failedReports.push(reports[i].message);
114					}
115				}
116			}
117			return failedReports;
118		}
119		function grab() {
120			if("object" == typeof arguments[0] && "string" == typeof arguments[1]) {
121				var parentObject = arguments[0];
122				var name = arguments[1];
123				var fullName = "[local]." + name;
124				return grabFunction(fullName, parentObject[name], parentObject);
125			} else {
126				var grabbed = null;
127				eval("grabbed = " + arguments[0]);
128				if("function" == typeof grabbed) {
129					var functionGrab = grabFunction(arguments[0], grabbed);
130					eval("grabbed = " + arguments[0]);
131					grabObject(arguments[0], grabbed);
132					return functionGrab;
133				} else if("object" == typeof grabbed) {
134					return grabObject(arguments[0], grabbed);
135				}
136				return null;
137			}
138		}
139		function grabFunction(fullName, grabbed, parentObject) {
140			if(parentObject == null) {
141				parentObject = window;
142			}
143			var functionName = fullName;
144			var nameParts = fullName.split(".");
145			if(nameParts[0] == "[local]") {
146				functionName = nameParts[1];
147			} else if(nameParts.length > 1) {
148				functionName = nameParts.pop();
149				if(parentObject == window) {
150					var parentName = nameParts.join(".");
151					eval("parentObject = " + parentName);
152				}
153			}
154			functionGrabs[fullName] = new FunctionGrab(functionName, grabbed, parentObject);
155			return functionGrabs[fullName];
156		}
157		function grabObject(name, grabbed) {
158			objectGrabs[name] = new ObjectGrab(name, grabbed);
159			return objectGrabs[name];
160		}
161		function create(objectName, functionNames) {
162			var mockObject = {};
163			for(var i=0; i<functionNames.length; i++) {
164				mockObject[functionNames[i]] = function() {};
165				var fullName = objectName+"."+functionNames[i];
166				grabFunction(fullName, mockObject[functionNames[i]], mockObject);
167			}
168			return mockObject;
169		}
170		function inspect(name) {
171			return findGrab(name);
172		}
173		function expect(name) {
174			if(findGrab(name) == null) {
175				grab(name);
176			}
177			currentExpectation = findGrab(name).expect().once();
178			return currentExpectation;
179		}
180		function verify(name) {
181			if(findGrab(name) == null) {
182				grab(name);
183			}
184			currentExpectation = findGrab(name).expect().once();
185			return currentExpectation;
186		}
187		function report(name, expectation) {
188			return findGrab(name).report(expectation, name);
189		}
190		function reportAll(name) {
191			return findGrab(name).reportAll(name);
192		}
193		function findGrab(name) {
194			var parts = name.split(".");
195			if(parts.length == 1 && functionGrabs[name] != null) {
196				return functionGrabs[name];
197			} else if(parts.length == 1 && objectGrabs[name] != null) {
198				return objectGrabs[name];
199			} else {
200				if(functionGrabs[name] != null) {
201					return functionGrabs[name];
202				}
203				if(objectGrabs[name] != null) {
204					return objectGrabs[name];
205				}
206				if(objectGrabs[parts[0]] != null) {
207					return objectGrabs[parts[0]].examine(parts[1]);
208				}
209				return undefined;
210			}
211		}
212		function resetGrabs() {
213			for(var g in functionGrabs) {
214				functionGrabs[g].reset();
215			}
216			for(var g in objectGrabs) {
217				objectGrabs[g].reset();
218			}
219		}
220	} // END Jack()
221
222
223	/**
224	 * @functionName      Name of grabbed function
225	 * @grabbedFunction   Reference to grabbed function
226	 * @parentObject      The object the function was grabbed from
227	 */
228	function FunctionGrab(functionName, grabbedFunction, parentObject) {
229		var invocations = [];
230		var specifications = [];
231		var emptyFunction = function(){};
232
233		init();
234		return {
235			'times': function() { return invocations.length; },
236			'reset': reset,
237			'expect': expect,
238			'specify': specify,
239			'report': report,
240			'reportAll': reportAll,
241			'mock': mock,
242			'stub': stub,
243			'arguments': getArguments,
244			'name': function() { return functionName }
245		};
246
247		function init() {
248			var original = parentObject[functionName];
249			var handler = function() {
250				return handleInvocation.apply(this,arguments);
251			}
252			for(var prop in original) {
253				handler[prop] = original[prop];
254			}
255			parentObject[functionName] = handler;
256		}
257		function handleInvocation() {
258			var invocation = new FunctionSpecification();
259			for(var i=0; i<arguments.length; i++) {
260				invocation.whereArgument(i).is(arguments[i]);
261			}
262			invocations.push(invocation);
263			var specification = findSpecificationFor(invocation);
264			if(specification == null) {
265				return grabbedFunction.apply(this, arguments);
266			} else if(specification.hasMockImplementation()) {
267				return specification.invoke.apply(this, arguments);
268			} else {
269				specification.invoke.apply(this, arguments);
270				return grabbedFunction.apply(this, arguments);
271			}
272		}
273		function matchInvocationsToSpecifications() {
274			for(var i=0; i<invocations.length; i++) {
275				var spec = findSpecificationFor(invocations[i]);
276				if(spec != null) {
277
278				}
279			}
280		}
281		function findSpecificationFor(invocation) {
282			for(var i=0; i<specifications.length; i++) {
283				var specification = specifications[i];
284				if(invocation.satisfies(specification)) {
285					return specification;
286				}
287			}
288			return null;
289		}
290		function isArgumentContstraintsMatching(invocation, expectation) {
291			var constr = expectation._argumentConstraints;
292			var arg = invocation.arguments;
293			if(constr == null) {
294				return true;
295			} else if(constr.length != arg.length) {
296				return false;
297			} else {
298				for(var i=0; i<constr.length; i++) {
299					if(!constr[i]) { continue; }
300					for(var j=0; j<constr[i].length; j++) {
301						if(typeof constr[i][j] == "function" && !constr[i][j](arg[i])) {
302							return false;
303						}
304					}
305				}
306				return true;
307			}
308		}
309		function reset() {
310			parentObject[functionName] = grabbedFunction;
311		}
312		function specify() {
313			var spec = new FunctionSpecification();
314			spec._id = specifications.length;
315			specifications.push(spec);
316			return spec;
317		}
318		function verify() {
319
320		}
321		function expect() {
322			return specify();
323		}
324		function mock(implementation) {
325			return expect().mock(implementation);
326		}
327		function stub() {
328			return expect();
329		}
330		function parseTimes(expression) {
331			var result = 0;
332			if("number" == typeof expression) {
333				result = expression;
334			} else if("string" == typeof expression) {
335				var parts = expression.split(" ");
336				result = parseInt(parts[0]);
337			}
338			return result;
339		}
340		function reportAll(fullName) {
341			var reports = [];
342			for(var i=0; i<specifications.length; i++) {
343				reports.push(report(specifications[i], fullName));
344			}
345			return reports;
346		}
347		function report(specification, fullName) {
348			if(specification == null) {
349				if(specifications.length == 0) {
350					var spec = specify().never();
351					for(var i=0; i<invocations.length; i++) {
352						spec.invoke();
353					}
354				}
355				specification = specifications[0];
356			}
357			var report = {};
358			report.expected = specification.invocations().expected;
359			report.actual = specification.invocations().actual;
360			report.success = specification.testTimes(report.actual);
361			report.fail = !report.success;
362			if(report.fail) {
363				report.message = "Expectation failed: " + specification.describe(fullName);
364			}
365			return report;
366		}
367		function generateReportMessage(report, fullName, argumentsDisplay) {
368			return report.messageParts.template
369					.replace("{name}",fullName)
370					.replace("{arguments}",argumentsDisplay)
371					.replace("{quantifier}",report.messageParts.quantifier)
372					.replace("{expected}",report.expected)
373					.replace("{actual}",report.actual);
374		}
375		function getArgumentsDisplay(expectation) {
376			if(expectation == null) {
377				return "";
378			}
379			var displayValues = [];
380			var constraints = expectation._argumentConstraints;
381			if(constraints == null) {
382				return "";
383			} else {
384				for(var i=0; i<constraints.length; i++) {
385					if(constraints[i] != null) {
386						displayValues.push(constraints[i][0].display);
387					} else {
388						displayValues.push('[any]');
389					}
390				}
391				return displayValues.join(', ');
392			}
393		}
394		function getArguments() {
395			return invocations[0].getArgumentValues();
396		}
397	} // END FunctionGrab()
398
399
400	/**
401	 *
402	 */
403	function ObjectGrab(objectName, grabbedObject) {
404		var grabs = {};
405
406		init();
407		return {
408			'examine': getGrab,
409			'report': report,
410			'getGrab': getGrab,
411			'getGrabs': function() {  return grabs },
412			'reset': reset
413		};
414
415		function init() {
416			for(key in grabbedObject) {
417				var property =  grabbedObject[key];
418				if("function" == typeof property) {
419					grabs[key] = new FunctionGrab(key, property, grabbedObject);
420				}
421			}
422		}
423		function report(name) {
424			var allReports = [];
425			for(var g in grabs) {
426				var reports = grabs[g].reportAll(name+"."+grabs[g].name());
427				for(var i=0; i<reports.length; i++) {
428					allReports.push(reports[i]);
429				}
430			}
431			return allReports;
432		}
433		function getGrab(name) {
434			return grabs[name];
435		}
436		function reset() {
437			for(var g in grabs) {
438				grabs[g].reset();
439			}
440		}
441	}
442
443
444	/**
445	 *
446	 */
447	function Environment() {
448		var reportingEnabled = true;
449		init();
450		return {
451			'isJSSpec': isJSSpec,
452			'isScriptaculous': isScriptaculous,
453			'isQunit': isQunit,
454			'isJsTestDriver': isJsTestDriver,
455			'isYuiTest': isYuiTest,
456			'report': report,
457			'disableReporting': function() { reportingEnabled = false; },
458			'enableReporting': function() { reportingEnabled = true; },
459			'reset': function() {}
460		}
461		function init() {
462
463		}
464		function isJSSpec() {
465			return window.JSSpec != null;
466		}
467		function isScriptaculous() {
468			return window.Test != null && window.Test.Unit != null && window.Test.Unit.Runner != null;
469		}
470		function isQunit() {
471			return window.QUnit != null;
472		}
473		function isJsTestDriver() {
474			return window.jstestdriver != null;
475		}
476		function isYuiTest() {
477			var y = window.YAHOO;
478			return y != null && y.tool != null && y.tool.TestCase != null;
479		}
480		function report(message) {
481			if(!reportingEnabled) { return; }
482			if(isYuiTest()) {
483				YAHOO.util.Assert.fail(message);
484			} else if(isJsTestDriver()) {
485				fail(message);
486			} else if(isScriptaculous()) {
487				throw new Error(message);
488			} else if(isQunit()) {
489				ok(false, message);
490			} else if(isJSSpec()) {
491				throw new Error(message);
492			}
493		}
494	}
495
496	/**
497	 *
498	 */
499	function Util() {
500		return {
501			'displayValue': displayValue
502		}
503
504		function displayValue() {
505			var value = arguments[0];
506			var prefix = "";
507			if(arguments.length > 1) {
508				value = arguments[1];
509				prefix = arguments[0];
510			}
511			if(value == undefined) {
512				return displayValueNullOrUndefined(value);
513			}
514			var display = displayValueDefault(value);
515			if('string' == typeof value) {
516				display = displayValueString(value);
517			} else if(value.constructor == Array) {
518				display = displayValueArray(value);
519			} else if(value.constructor == RegExp) {
520				display = displayValueRegExp(value);
521			} else if('object' == typeof value) {
522				display = displayValueObject(value);
523			}
524			return prefix + display;
525		}
526		function displayValueDefault(value) {
527			return value.toString();
528		}
529		function displayValueString(value) {
530			return "'" + value + "'";
531		}
532		function displayValueArray(value) {
533			var displayValues = [];
534			for(var i=0; i<value.length; i++) {
535				displayValues.push(displayValue(value[i]));
536			}
537			return "[" + displayValues.join(",") + "]";
538		}
539		function displayValueNullOrUndefined(value) {
540			if(value === undefined) {
541				return "undefined";
542			} else if(value === null) {
543				return "null";
544			}
545		}
546		function displayValueRegExp(value) {
547			return value.toString();
548		}
549		function displayValueObject(value) {
550			var keyValues = [];
551			for(var p in value) {
552				keyValues.push(p + ':' + displayValue(value[p]));
553			}
554			return '{' + keyValues.join(',') + '}';
555		}
556	}
557
558	/**
559	 *
560	 */
561	function FunctionSpecification() {
562		var constraints = null;
563		var argumentValues = [];
564		var mockImplementation = null;
565		var timing = {actual: 0, expected: 1, modifier: 0};
566
567		var api = createApi();
568		return api;
569
570		function createApi() {
571			var api = {};
572			mixinMatchers(api);
573			mixinTiming(api);
574			api.test = test;
575			api.testTimes = testTimes;
576			api.satisfies = satisfies;
577			api.invoke = invoke;
578			api.mock = mock;
579			api.stub = stub;
580			api.returnValue = returnValue;
581			api.returnValues = returnValues;
582			api.describe = describe;
583			api.invocations = invocations;
584			api.getArgumentValues = getArgumentValues;
585			api.hasMockImplementation = hasMockImplementation;
586			return api;
587		}
588		function mixinMatchers(api) {
589			api.whereArgument = function(argIndex) {
590				var collected = {};
591				for(var name in jack.matchers) {
592					addMatcher(argIndex, name, collected)
593				}
594				return collected;
595			}
596			api.withArguments = function() {
597				for(var i=0; i<arguments.length; i++) {
598					api.whereArgument(i).is(arguments[i]);
599				}
600				return api;
601			}
602			api.withNoArguments = function() { constraints = []; return api; }
603			return api;
604
605			function addMatcher(argIndex, name, collection) {
606				collection[name] = function() {
607					addConstraint(argIndex, jack.matchers[name], arguments);
608					if(name == "is") {
609						addArgumentValue(argIndex, arguments[0]);
610					}
611					return api;
612				}
613			}
614		}
615		function mixinTiming(api) {
616			api.exactly = function(times) {
617				timing.expected = parseTimes(times);
618				return api;
619			}
620			api.once = function() {
621				timing.expected = 1;
622				return api;
623			}
624			api.atLeast = function(times) {
625				timing.expected = parseTimes(times);
626				timing.modifier = 1;
627				return api;
628			}
629			api.atMost = function(times) {
630				timing.expected = parseTimes(times);
631				timing.modifier = -1;
632				return api;
633			}
634			api.never = function() {
635				timing.expected = 0;
636				return api;
637			}
638
639			function parseTimes(times) {
640				return parseInt(times);
641			}
642		}
643		function addArgumentValue(index, value) {
644			argumentValues[index] = value;
645		}
646		function addConstraint(argIndex, matcher, matcherArguments) {
647			createConstraintsArrayIfNull(argIndex);
648			var constraint = function(value) {
649				var allArguments = [value];
650				for(var i=0; i<matcherArguments.length; i++) {
651					allArguments.push(matcherArguments[i]);
652				}
653				var test = matcher.apply(null, allArguments);
654				return test.result;
655			}
656			constraints[argIndex].push(constraint);
657			constraints[argIndex].describe = function() {
658				var allArguments = [""];
659				for(var i=0; i<matcherArguments.length; i++) {
660					allArguments.push(matcherArguments[i]);
661				}
662				return matcher.apply(null, allArguments).expected;
663			}
664		}
665		function createConstraintsArrayIfNull(argIndex) {
666			if(constraints == null) {
667				constraints = [];
668			}
669			if(constraints[argIndex] == null) {
670				constraints[argIndex] = [];
671			}
672		}
673		function test() {
674			var result = true;
675			if(constraints != null) {
676				if(constraints.length != arguments.length) {
677					result = false;
678				} else {
679					for (var i = 0; i < constraints.length; i++) {
680						var oneArgumentsConstraints = constraints[i];
681						if (oneArgumentsConstraints != null) {
682							for (var j = 0; j < oneArgumentsConstraints.length; j++) {
683								var constraint = oneArgumentsConstraints[j];
684								if (constraint && !constraint(arguments[i])) {
685									result = false;
686								}
687							}
688						}
689					}
690				}
691			}
692			return result;
693		}
694		function testTimes(times) {
695			if(timing.modifier == 0) {
696				return times == timing.expected;
697			} else if(timing.modifier == 1) {
698				return times >= timing.expected;
699			} else if(timing.modifier == -1) {
700				return times <= timing.expected;
701			}
702		}
703		function satisfies(other) {
704			return other.test.apply(this, argumentValues);
705		}
706		function invoke() {
707			timing.actual++;
708			if(mockImplementation != null) {
709				return mockImplementation.apply(this, arguments);
710			}
711		}
712		function mock(implementation) {
713			mockImplementation = implementation;
714			return api;
715		}
716		function stub() {
717			mockImplementation = function() {};
718			return api;
719		}
720		function returnValue(value) {
721			mockImplementation = function() {
722				return value;
723			}
724		}
725		function returnValues() {
726			var values = [], orig = this;
727			for (var i = 0, len = arguments.length; i < len; i++) { values.push(arguments[i]); }
728			mockImplementation = function() {
729				return values.shift();
730			};
731		}
732		function hasMockImplementation() {
733			return mockImplementation != null;
734		}
735		function invocations() {
736			return {
737				actual: timing.actual,
738				expected: timing.expected
739			};
740		}
741		function getArgumentValues() {
742			return argumentValues;
743		}
744		function describe(name) {
745			return name +"(" + describeConstraints() + ") " + describeTimes();
746		}
747		function describeConstraints() {
748			if(constraints == null) {
749				return "";
750			}
751			var descriptions = [];
752			for(var i=0; i<constraints.length; i++) {
753				if(constraints[i] != null) {
754					descriptions.push(constraints[i].describe());
755				} else {
756					descriptions.push("[any]");
757				}
758			}
759			return descriptions.join(", ");
760		}
761		function describeTimes() {
762			var description = timing.expected;
763			if(timing.expected == 1) {
764				description += " time";
765			} else {
766				description += " times";
767			}
768			if(timing.modifier == 0) {
769				description = "expected exactly " + description;
770			} else if(timing.modifier > 0) {
771				description = "expected at least " + description;
772			} else if(timing.modifier < 0) {
773				description = "expected at most " + description;
774			}
775			description += ", called " + timing.actual + " time";
776			if(timing.actual != 1) {
777				description += "s";
778			}
779			return description;
780		}
781	}
782
783
784	/**
785	 *
786	 */
787	function Matchers() {
788		return {
789			'is':
790				function(a, b) {
791					return result(a==b, a, '', b);
792				},
793			'isNot':
794				function(a, b) {
795					return result(a!=b, a, 'not:', b);
796				},
797			'isType':
798				function(a, b) {
799					return result(b == typeof a, a, 'type:', b);
800				},
801			'matches':
802				function(a, b) {
803					return result(b.test(a), a, 'matching:', b)
804				},
805			'hasProperty':
806				function(a, b, c) {
807					var match = c ? a[b]==c : a[b]!=undefined;
808					var bDisplay = b;
809					if(c != null) {
810						bDisplay = {};
811						bDisplay[b] = c;
812					}
813					return result(match, a, 'property:', bDisplay)
814				},
815			'hasProperties':
816				function(a, b) {
817					var match = true;
818					for(var p in b) {
819						if(a[p] != b[p]) {
820							match = false;
821						}
822					}
823					return result(match, a, 'properties:', b);
824				},
825			'isGreaterThan':
826				function(a, b) {
827					return result(b < a, a, '>', b);
828				},
829			'isLessThan':
830				function(a, b) {
831					return result(b > a, a, '<', b);
832				},
833			'isOneOf':
834				function() {
835					var a = arguments[0];
836					var b = [];
837					for(var i=1; i<arguments.length; i++) {
838						b.push(arguments[i]);
839					}
840					var match = false;
841					for(var i=0; i<b.length; i++) {
842						if(b[i] == a) {
843							match = true;
844						}
845					}
846					return result(match, a, 'oneOf:', b);
847				}
848		}
849
850		function result(match, actual, prefix, expected) {
851			return {
852				result: match,
853				actual: jack.util.displayValue(actual),
854				expected: jack.util.displayValue(prefix, expected)
855			}
856		}
857	}
858
859})(); // END HIDING FROM GLOBAL SCOPE
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903