jQuery(document).ready(function(){
	
	module("PASS");
	
	test("without shortcut", function() {    
		var myObj = {sayHello:function(){}};
	    var mock = new jqMock.Mock(myObj, "sayHello");
	    mock.modify().args(jqMock.is.anyOf(["Lisa","Bart","Maggie"]));
	    mock.modify().args("Homer").multiplicity(jqMock.expects.atLeast(1));    
	    myObj.sayHello("Lisa");    
	    myObj.sayHello("Homer");    
	    mock.verifyAll();
	});
	
	jqMock.addShortcut();
	
	test("mocking native dialog", function() {
		var confirmMock = new jqMock.Mock(window, "confirm");
		confirmMock.modify().args("are you sure?").returnValue(true);
		confirmMock.modify().args("are you sure 2?").returnValue(false);
		var b1 = confirm("are you sure?");
		ok(b1, "should be true");
		var b2 = confirm("are you sure 2?");
		ok(!b2, "should be true (2)");	
		confirmMock.verifyAll();
		confirmMock.restore();
		
		var alertMock = new jqMock.Mock(window, "alert");
		alertMock.modify().args("are you sure?").returnValue();
		alert("are you sure?");
		alertMock.verifyAll();
		alertMock.restore();
	});
	
	test("args() expectation", function() {
	
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		
		mock.modify().args(16);
		mock.modify().args([4,5,6]);
		var obj = {a:'a', b: ['x', 'y'], func : function() {}};
		mock.modify().args(obj);
		mock.modify().args(null);
		mock.modify().args(undefined);
		mock.modify().args(16,[4,5,6], obj);
		mock.modify(); // default no arg, multiplicity 1
		
		var objActual = {a:3, fn:function(){}, b:{x:'x',y:'y'}};
		var objExpect = {b:{x:'x',y:'y'}, a:3, fn:is.anything};
		mock.modify().args(objExpect);
		myobj.fn(objActual);
		
		myobj.fn(16);
		myobj.fn([4,5,6]);
		myobj.fn(obj);
		myobj.fn(null);
		myobj.fn();
		var b;
		myobj.fn(b);
		myobj.fn(16,[4,5,6], obj);
		
		mock.verifyAll();
	});
	
	test("Expression is()", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		
		mock.modify().args(is.anything).multiplicity(4);
		myobj.fn("a");
		myobj.fn(3);
		myobj.fn([1,2,3]);
		myobj.fn({a:[1,2,3],b:{c:'c'}});
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args({a:'apple', x: is.anything, b:'banana'}).multiplicity(2);
		myobj.fn({a:'apple', x: 'xxx', b:'banana'});
		myobj.fn({a:'apple', b:'banana'});
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(is.not('x')).multiplicity(4);
		myobj.fn("y");
		myobj.fn(1);
		myobj.fn(['x','y']);
		myobj.fn({x:'x', y:'y'});
		mock.verifyAll();
		mock.reset();
		
		var MyObject = function MyObject(){};
		mock.modify().args(is.instanceOf(MyObject));
		mock.modify().args(is.instanceOf(Array));
		myobj.fn(new MyObject());
		myobj.fn([1,2,3]);
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(is.not(null));
		mock.modify().args(is.not(undefined));	
		myobj.fn("something");
		myobj.fn(null);
		mock.verifyAll();
		mock.reset();	
		
		mock.modify().args(is.objectThatIncludes({a:'apple', b:'banana'}));
		myobj.fn({a:'apple', fn:function(){}, b:'banana'});
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(is.regex(/^abc[0-9]*xyz$/));
		mock.modify().args(is.not(is.regex(/[0-9]/)));
		myobj.fn("abc12345xyz");
		myobj.fn("abc");
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(is.anyOf(["a", "b", "c",null, undefined])).multiplicity(5);
		myobj.fn("a");
		myobj.fn(null);
		myobj.fn("c");
		myobj.fn(undefined);
		myobj.fn("b");
		mock.verifyAll();
		mock.reset();	
		
		mock.modify().args(is.allOf([is.not("a"), is.not("b")])).multiplicity(2);
		mock.modify().args(is.allOf([is.not(null), is.not(undefined)]));
		myobj.fn("x");
		myobj.fn("y");
		myobj.fn("z");
		mock.verifyAll();
		mock.reset();
			
		var isEven = function(arg) {
			return arg % 2 == 0 ;
		};
		mock.modify().args(is.custom(isEven)).multiplicity(3);
		myobj.fn(2);
		myobj.fn(4);
		myobj.fn(6);
		mock.verifyAll();
		mock.reset();	
	});
	
	test("expectation resolution", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		
		mock.modify().args(is.not('x'));
		mock.modify().args(is.not('y')); 
		mock.modify().args(is.anything);
		myobj.fn(1);
		myobj.fn(2);
		myobj.fn(3);
		mock.verifyAll();
		mock.reset();
		
		// this is an RMock behaviour.
		mock.modify().multiplicity(2).args(is.not("y"));
		mock.modify().multiplicity(expects.atLeast(2)).args("x");
		for(var i=0; i<4; i++) myobj.fn("x");
		mock.verifyAll();
		mock.reset();
		
		mock.modify().multiplicity(expects.times(2,3)).args("x");
		mock.modify().multiplicity(expects.exactly(1)).args(is.not("y"));
		mock.modify().multiplicity(expects.atMost(1)).args("x");
		for(var i=0; i<5; i++) myobj.fn("x");
		mock.verifyAll();
		mock.reset();	
	});
	
	test("ordered expectations", function() {
	
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		mock.setOrdered(true);
		
		mock.modify().args(1);
		mock.modify().args(2);
		mock.modify().args(3);
		myobj.fn(1);
		myobj.fn(2);
		myobj.fn(3);
		mock.verifyAll();
		mock.reset();	
		
		mock.modify().args(4);
		myobj.fn(4);
		mock.verifyAll();
		mock.reset();	
		
		mock.modify().args('x');
		mock.modify().args('y');
		mock.modify().args('x');
		myobj.fn('x');
		myobj.fn('y');
		myobj.fn('x');
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(is.anything);
		mock.modify().args(is.not('y'));
		mock.modify().args(is.instanceOf(Array));
		myobj.fn({});
		myobj.fn('x');
		myobj.fn([1,2,3]);
		mock.verifyAll();
		mock.reset();		
	});
	
	test("unexpected invocation, but using verify() only", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");	
		mock.modify().args(1);
		mock.modify().args(2);
		mock.modify().args(3);
		myobj.fn(3);
		myobj.fn(2);
		myobj.fn(5);  // unexpected, but we dont check
		myobj.fn(4);  // unexpected, but we dont check
		myobj.fn(1);
		mock.verify();
	});
	
	test("returnValue and intercepting", function() {
		var myobj = {sum:function(a,b){return a+b;}};
		var mock = new jqMock.Mock(myobj, "sum");
		mock.modify().args(5,6).returnValue(100);  // change the returnValue
		mock.modify().args(5,6); // don't intercept
		equals(myobj.sum(5,6) , 100);
		equals(myobj.sum(5,6) , 11);
		mock.restore();
		equals(myobj.sum(11,12) , 23);
	});
	
	test("throwException", function() {
		expect(2);
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		mock.modify().args().throwException("my exception");
		var myError = new Error("some error");
		mock.modify().args(1).throwException(myError);	
		try {
			myobj.fn();
		} catch (ex) {
			equals(ex, "my exception");
		}
		try {
			myobj.fn(1);
		} catch (ex) {
			equals(ex.message, "some error");
		}	
	});
	
	test("multiplicity", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
	
		mock.modify().args().multiplicity(expects.exactly(1));
		mock.modify().args('a').multiplicity(expects.times(3));
		mock.modify().args('c').multiplicity(2);
		mock.modify().args('b','bbb').multiplicity(expects.times(2,3));
		mock.modify().args({p:'p'},[7,8]);
		mock.modify().args('y').multiplicity(expects.atLeast(1));
		mock.modify().args('z').multiplicity(expects.atMost(4));	
		mock.modify().args('foo').multiplicity(expects.atMost(0));
		mock.modify().args('zero1').multiplicity(0);
		mock.modify().args('zero2').multiplicity(expects.exactly(0));
		mock.modify().args('zero3').multiplicity(expects.times(0,2));
	
		myobj.fn({p:'p'},[7,8]);
		myobj.fn('a');
		myobj.fn();
		myobj.fn('a');		
		myobj.fn('a');
		myobj.fn('c');
		myobj.fn('c');
		myobj.fn('b','bbb');
		myobj.fn('z');	
		myobj.fn('y');	
		myobj.fn('y');	
		myobj.fn('b','bbb');
		myobj.fn('z');	
		myobj.fn('z');	
		myobj.fn('z');	
		mock.verifyAll();
		mock.reset();
	});
	
	test("tostring", function() {
		equals(jqMock.tostring(3), "3");
		equals(jqMock.tostring("a"), "'a'");
		equals(jqMock.tostring([]), "[]");
		equals(jqMock.tostring([1,2,3]), "[1,2,3]");
		equals(jqMock.tostring([1,2,"x"]), "[1,2,'x']");	
		equals(jqMock.tostring(function(){}), "function()");
		equals(jqMock.tostring({}), "{}");
		equals(jqMock.tostring({a:1,b:2,c:'cc'}), "{a:1,b:2,c:'cc'}");
		var x;
		equals(jqMock.tostring(x), "undefined");
		equals(jqMock.tostring(null), "null");
	
		equals(jqMock.tostring(is.anything), "is.anything");
		equals(jqMock.tostring(is.not('x')), "is.not('x')");
		equals(jqMock.tostring(is.instanceOf(Array)), "is.instanceOf(Array)");
		equals(jqMock.tostring(is.objectThatIncludes({a:'a'})), "is.objectThatIncludes({a:'a'})");
		equals(jqMock.tostring(is.regex(/^[0-9]*/)), "is.regex(/^[0-9]*/)");
		equals(jqMock.tostring(is.anyOf([is.not('a'), is.not('b')])), "is.anyOf([is.not('a'),is.not('b')])");
		equals(jqMock.tostring(is.allOf([is.not(null), is.not(undefined)])), "is.allOf([is.not(null),is.not(undefined)])");
		equals(jqMock.tostring(is.exception({name: "ReferenceError", message: "blah is not defined", type: ReferenceError})), "is.exception({name:'ReferenceError',message:'blah is not defined',type:is.instanceOf(ReferenceError)})");
		equals(jqMock.tostring(is.exception("foo")), "is.exception({message:'foo'})");
		
		// NOTE: rhino has error during for(var i : obj) which returns the wrong ordering due to HashMap implementation
		var obj = {c:{x:'x',y:['xx',99]}, d:[1,2,{p:'p',q:5}], fn:function(){}, a:3, b:'bob'};
		var objStr = "{c:{x:'x',y:['xx',99]},d:[1,2,{p:'p',q:5}],fn:function(),a:3,b:'bob'}"
		equals(jqMock.tostring(obj), objStr);
	});
	
	test("circular reference", function() {
		var a={}; 
		var b={};
		var x1={'x':a};
		var x2={'x':b};
		a['x'] = x1;		 
		b['x'] = x2;
	
		var aa=[]; 
		var bb=[];
		var xx1=[aa];
		var xx2=[bb];
		aa[0] = xx1;		 
		bb[0] = xx2;			
				
		expect(6);
	
		equals(jqMock.tostring(a), "{x:{x:...}}");
		equals(jqMock.tostring(aa), "[[...]]");
			
		try {
			jqMock.assertThat(a,b);
		} catch (ex) {
			equals(ex.message, "Circular Reference detected in the object!", "ciruclar ref in object");
		}
		try {
			jqMock.assertThat(a,b);
		} catch (ex) {
			equals(ex.message, "Circular Reference detected in the object!", "ciruclar ref in object");
		}
		try {
			var myobj = {fn:function(){}};
			var mock = new jqMock.Mock(myobj, "fn");
			mock.modify().args(a).multiplicity(1);
			myobj.fn(b);
		} catch (ex) {
			equals(ex.message, "Circular Reference detected in the object!", "ciruclar ref in object");
		}
		try {
			var myobj = {fn:function(){}};
			var mock = new jqMock.Mock(myobj, "fn");
			mock.modify().args(aa).multiplicity(1);
			myobj.fn(bb);
		} catch (ex) {
			equals(ex.message, "Circular Reference detected in the object!", "ciruclar ref in object");
		}
	});
	
	test("object assertion", function() {
		jqMock.assertThat(window, window, "assertThat(window), reference equality");
		jqMock.assertThat(3,3);
		jqMock.assertThat("a","a");
		jqMock.assertThat([1,2,3],[1,2,3]);	
		jqMock.assertThat({},{}, "{}");
		jqMock.assertThat([{a:'a'},{b:'b',d:'d'}],[{a:'a'},{d:'d', b:'b'}]);
		jqMock.assertThat({b:'b',x:[1,2,3],d:'d'},{d:'d',x:[1,2,3],b:'b'});
		var fn = function(n) {return n+1;};
		jqMock.assertThat(fn, fn);
		
		var objActual = {a:3, b:{x:'x',y:'y'}, c:[7,8,9]};
		var objExpect = {b:{x:'x',y:'y'}, c:[7,8,9], a:3};
		jqMock.assertThat(objActual,objExpect);
		
		jqMock.assertThat([1,2,3], is.instanceOf(Array));
		jqMock.assertThat("foo", is.anyOf(['foo','bar']));
		jqMock.assertThat({x:'x', a:'a', y:'y'}, is.objectThatIncludes({a:'a'}));
		
		jqMock.assertThat(new Error("booya"), is.exception("booya"), "is.exception");
	});
	
	test("expectThatExceptionThrown", function() {
		// simple compare
		jqMock.expectThatExceptionThrown(function() {
			throw "blah";
		}, "blah");
		// using expression
		jqMock.expectThatExceptionThrown(function() {
			throw new Error("booya");
		}, is.allOf([is.instanceOf(Error), is.objectThatIncludes({'message':'booya'})]));	
		// using is.exception expression
		jqMock.expectThatExceptionThrown(function() {
			throw {"message":"bad exception"};
		}, is.exception({message: "bad exception"}));	
		jqMock.expectThatExceptionThrown(function() {
			throw new Error("bad exception 2");
		}, is.exception("bad exception 2"));		
		jqMock.expectThatExceptionThrown(function() {
			eval("blah");
		}, is.exception({name: "ReferenceError", message: "blah is not defined", type: ReferenceError}));
		function MyUserException (message) {
		  this.message=message;
		  this.name="UserException";
		}
		jqMock.expectThatExceptionThrown(function() {
			throw new MyUserException("booya");
		}, is.exception({name: "UserException", message: "booya", type: MyUserException}));
		// just that some expression is thrown
		jqMock.expectThatExceptionThrown(function() {
			throw new Error("anything");
		}, is.exception());	
	});
	
	test("error handling", function() {
		expect(15);
		var obj = {x:'x'};
		try {
			new jqMock.Mock(obj, "x");
		} catch(ex) {
			equals(ex.message, "Could not create Mock: x is not a function", "exception should be thrown");
		}
		var obj2;
		try {
			new jqMock.Mock(obj2, "x");
		} catch(ex) {
			equals( ex.message, "Could not create Mock: the object or function you specified is not defined, obj.x", "exception should be thrown");
		}	
		var obj3 = {};
		try {
			new jqMock.Mock(obj3, "x");
		} catch(ex) {
			equals(ex.message, "Could not create Mock: the object or function you specified is not defined, obj.x", "exception should be thrown");
		}
		var obj4 = {fn:function(){}};
		try {
			var mock = new jqMock.Mock(obj4, "fn");
			mock.verify();
		} catch(ex) {
			equals(ex.message, "Expectation setup error. You must set up some expectations", "exception should be thrown");
		}
		var alertMock = new jqMock.Mock(window, "alert");
		try {		
			ok(window.alert.isMock, "flag should be set");
			new jqMock.Mock(window, "alert");		
		} catch(ex) {
			equals(ex.message, "Could not create Mock: you tried to overwrite a function which is already a mock!", "exception should be thrown");
		}
		alertMock.restore();
		
		var obj5 = {fn:function(){}};
		var mock5 = new jqMock.Mock(obj5, "fn");
		try {
			mock5.modify().args(is.instanceOf('x'));
		} catch(ex) {
			equals(ex.message, "Invalid argument to is.instanceOf() , argument must be a Class", "exception should be thrown");
		}
		try {
			mock5.modify().args(is.regex('x'));
		} catch(ex) {
			equals(ex.message, "Invalid argument to is.regex() , argument must be regex", "exception should be thrown");
		}
		try {
			mock5.modify().args(is.anyOf('x'));
		} catch(ex) {
			equals(ex.message, "Invalid argument to is.anyOf() , argument must be an Array", "exception should be thrown");
		}
		try {
			mock5.modify().args(is.allOf('x'));
		} catch(ex) {
			equals(ex.message, "Invalid argument to is.allOf() , argument must be an Array", "exception should be thrown");
		}
		try {
			mock5.modify().args(is.custom('x'));
		} catch(ex) {
			equals(ex.message, "Invalid argument to is.custom() , argument must be a Function that returns a boolean value", "exception should be thrown");
		}
		try {
			mock5.modify().multiplicity("x");
		} catch(ex) {
			equals(ex.message, "Invalid argument for multiplicity()", "exception should be thrown");
		}
			
		try {
			jqMock.addShortcut();
		} catch(ex) {
			equals(ex.message, "Cannot add shortcuts, expect() or is() already exists in global scope!", "exception should be thrown");	
		}	
	
		try {
			is.exception(0);
		} catch(ex) {
			equals(ex.message, "Invalid argument to is.exception() , argument must be an object or string");
		}
		
		try {
			is.exception({type:'foo'});
		} catch(ex) {
			equals(ex.message, "Invalid argument to is.exception() , the type attribute of the parameter must be a Class");
		}
	});
	
	//====================================================================
	// The following tests check the failure behaviour of jqMock.
	// After running tests that record failures through the framework,
	// this code will scrape the resulting HTML to check the failures.
	// Note: does not support the drilldown doubleclick view
	//====================================================================
	
	var counter = 15; // number of tests in PASS module above
	
	var assertFailureCallbacks = []
	function assertFailure(callback) {
		assertFailureCallbacks.push(callback);
	}
	function assertFailMessages(expectedMessages) {
		var allAssertionsPassed = true;
		var createSpan = function(passes) {
			return $("<span></span>")
				.css("color", passes ? "green" : "#FF6E1F")
				.text(passes? " [PASS]" : " [FAIL]");
		};
		var testResult = $('ol#tests>li').eq(counter++);	
		var assertionResults = testResult.find("ol>li");
		assertionResults.each(function(i,x) {
			var tmp = document.createElement("div");
			tmp.innerHTML = expectedMessages[i];  // because of HTML entities
			var ok = (x.innerHTML==tmp.innerHTML);
			if (!ok) {
				allAssertionsPassed = false;
				if (console && console.error) {
					console.error("[" + counter + "] Actual:\n" + x.innerHTML);
					console.error("[" + counter + "] Expected:\n" + tmp.innerHTML);
				}
			}
			$(x).append(createSpan(ok));
			tmp = null;
		});
		createSpan(allAssertionsPassed).appendTo(testResult.find("strong"));
	}
	$(function() {
		for(var i=0; i<assertFailureCallbacks.length; i++) {
			assertFailureCallbacks[i]();
		}
		// scrape HTML to find fails, ignoring expected failures
		var failed = $('ol#tests>li.fail').filter(function(){
			return $(this).find('strong').text().indexOf(") [PASS]")<0;
		});
		$('h2#banner')[0].className = (failed.length>0 ? "fail" : "pass");
	});
	
	
	var unos = "Unordered expectations have not yet been satisfied:<br>";
	var onos = "Ordered expectations have not yet been satisfied:<br>";
	var noex = "No expectation matched for the following calls:<br>";
	var noex2 = "No expectation matched for the following calls which were setup as expectations:<br>"
	var br = "<br>";
	var s3 = "&nbsp;&nbsp;&nbsp;";
	
	
	// ----------------------  run the tests ----------------------
	module("FAIL");
	
	test("mocking native dialog", function() {
		var alertMock = new jqMock.Mock(window, "alert");
		alertMock.modify().args("some alert");
		window.alert("some other value");		
		alertMock.verifyAll();
		alertMock.restore();
	});
	assertFailure(function() {
		var expected = [];
		expected[0] = unos +
			"&rarr;0 (1) alert('some alert')" + br +
			noex +
			"alert('some other value')" + br;
		assertFailMessages(expected);
	});
	
	
	test("args() expectation", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		
		var objActual = {a:3, fn:function(){}, b:{x:'x',y:'y'}};
		var objExpect = {b:{x:'x',y:'zzzzz'}, a:3};
		mock.modify().args(objExpect);
		mock.modify().args(1,2,3);
		mock.modify().args();
		
		myobj.fn(objActual);
		myobj.fn(1,2);
		myobj.fn('foo');
				
		mock.verifyAll();
	});
	assertFailure(function() {
		var expected = [];
		expected[0] = unos +
			"&rarr;0 (1) fn({b:{x:'x',y:'zzzzz'},a:3})" +br+
			"&rarr;0 (1) fn(1,2,3)"+br+
			"&rarr;0 (1) fn()"+br+		
			noex+
			"fn({a:3,fn:function(),b:{x:'x',y:'y'}})"+br+
			"fn(1,2)"+br+
			"fn('foo')"+br;
		assertFailMessages(expected);
	});
	
	
	test("Expression is()", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
	
		mock.modify().args(is.not('x'));
		mock.verifyAll();
		mock.reset();
		
		var MyObject = function MyObject(){};
		mock.modify().args(is.instanceOf(MyObject));
		mock.modify().args(is.instanceOf(Array));
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(is.objectThatIncludes({a:'apple', b:'banana'}));
		myobj.fn({a:'apple'});
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(is.regex(/^[0-9]+/));
		myobj.fn("abc");
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(is.anyOf(["a", "b", "c"]));
		myobj.fn("d");
		mock.verifyAll();
		mock.reset();	
		
		mock.modify().args(is.allOf([is.not(null), is.not(undefined)]));
		myobj.fn(null);
		myobj.fn(undefined);
		mock.verifyAll();
		mock.reset();
			
		var isEven = function(arg) {
			return arg % 2 == 0 ;
		};
		mock.modify().args(is.custom(isEven)).multiplicity(2);
		myobj.fn(3);
		myobj.fn(2);
		mock.verifyAll();
		mock.reset();
			
	});
	assertFailure(function() {
		var expected = [];
		expected[0] = unos +
			"&rarr;0 (1) fn(is.not('x'))" +br;
		expected[1] = unos +		
			"&rarr;0 (1) fn(is.instanceOf(MyObject))" +br+
			"&rarr;0 (1) fn(is.instanceOf(Array))" +br;	
		expected[2] = unos +	
			"&rarr;0 (1) fn(is.objectThatIncludes({a:'apple',b:'banana'}))" + br+
			noex+
			"fn({a:'apple'})"+ br;
		expected[3] = unos +	
			"&rarr;0 (1) fn(is.regex(/^[0-9]+/))" + br +
			noex+
			"fn('abc')" + br;
		expected[4] = unos +
			"&rarr;0 (1) fn(is.anyOf(['a','b','c']))" + br + 
			noex+
			"fn('d')" + br;
		expected[5] = unos +
			"&rarr;0 (1) fn(is.allOf([is.not(null),is.not(undefined)]))" + br + 
			noex+
			"fn(null)" + br +
			"fn(undefined)" + br;		
		expected[6] = unos +
			"&rarr;1 (2) fn(is.custom(function))" + br + 
			noex+
			"fn(3)" + br;			
		assertFailMessages(expected);
	});
	
	test("expectation resolution", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		
		// this is RMock behaviour
		mock.modify().multiplicity(expects.atLeast(2)).args("x"); // atLeast will always match...
		mock.modify().multiplicity(2).args(is.not("y"));         // meaning this will never get matched
		myobj.fn("x");
		myobj.fn("x");
		myobj.fn("x");
		myobj.fn("x");
		mock.verifyAll();
		mock.reset();
		
		// even without verifying unexpected invocations, should fail
		mock.modify().args("x").multiplicity(2);
		myobj.fn("x");
		myobj.fn("x");
		myobj.fn("x");  // 3rd invoc, should fail
		myobj.fn("y");  // since using verify(), should ignore this one
		mock.verify();	
	});
	assertFailure(function() {
		var expected = [];
		expected[0] = unos +
			s3 + "4 (2..*) fn('x')" + br +
			"&rarr;0 (2) fn(is.not('y'))" + br;
		expected[1] = noex2 + "fn('x')" + br;
		assertFailMessages(expected);
	});
	
	
	
	
	test("ordered expectations", function() {
	
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		mock.setOrdered(true);
		
		mock.modify().args(1);
		mock.modify().args(2);
		mock.modify().args(3);
		myobj.fn(1);
		myobj.fn(3);
		myobj.fn(2);
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args('x');
		mock.modify().args('y');
		mock.modify().args('x');
		myobj.fn('x');
		myobj.fn('x');
		myobj.fn('y');
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args(1);
		mock.modify().args(2);
		mock.modify().args(3);
		myobj.fn(1);
		myobj.fn(3);
		myobj.fn(2);
		mock.verify();  // verify() only
		mock.reset();			
	});
	assertFailure(function() {
		var expected = [];
		expected[0] = onos +
			s3 + "1 (1) fn(1)" + br +
			"&rarr;0 (1) fn(2)" + br+
			"&rarr;0 (1) fn(3)" + br+
			noex+
			"fn(3)" + br+
			"fn(2)" + br;
		expected[1] = onos +
			s3 + "1 (1) fn('x')" + br +
			"&rarr;0 (1) fn('y')" + br+
			"&rarr;0 (1) fn('x')" + br+
			noex+
			"fn('x')" + br+
			"fn('y')" + br;		
		expected[2] = onos +
			s3 + "1 (1) fn(1)" + br +
			"&rarr;0 (1) fn(2)" + br+
			"&rarr;0 (1) fn(3)" + br;	
		assertFailMessages(expected);
	});
	
	
	test("using verify() does not print out unexpected section", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
		
		var objActual = {a:3, fn:function(){}, b:{x:'x',y:'y'}};
		var objExpect = {b:{x:'x',y:'zzzzz'}, a:3};
		mock.modify().args(objExpect);
		mock.modify().args(1,2,3);
		mock.modify().args();
		
		myobj.fn(objActual);
		myobj.fn(1,2);
		myobj.fn('foo');
				
		mock.verify();
	});
	assertFailure(function() {
		var expected = [];
		expected[0] = unos +
			"&rarr;0 (1) fn({b:{x:'x',y:'zzzzz'},a:3})" +br+
			"&rarr;0 (1) fn(1,2,3)"+br+
			"&rarr;0 (1) fn()"+br;
		assertFailMessages(expected);
	});
	
	
	test("object assertion", function() {
		jqMock.assertThat(1,3);
		jqMock.assertThat(3,"3");
		jqMock.assertThat("b","a");
		jqMock.assertThat([1,2,3],[1,2]);
		jqMock.assertThat([1,2,3],[3,2,1]);
		jqMock.assertThat([1,2,3],[1,2,"x"]);	
		jqMock.assertThat([1,2,3,4,5,6,7],[1,2,"x",4,5,6,7]);
		jqMock.assertThat([1,2,3,4,5,6,7],[1,2,"3",4,5,6,7]);
		jqMock.assertThat({a:'a',b:'b',c:'c'},{a:'a',b:'b'});
		var objActual = {a:3, fn:function(){}, b:{x:'x',y:'y'}};
		var objExpect = {b:{x:'x',y:'zzzzz'}, a:3};
		jqMock.assertThat(objActual,objExpect);
		jqMock.assertThat(3, is.instanceOf(Array));
	});
	assertFailure(function() {
		var expected = [
			"object did not match expectation, actual: 1, expected: 3",
			"object did not match expectation, actual: 3, expected: '3'",
			"object did not match expectation, actual: 'b', expected: 'a'",
			"object did not match expectation, actual: [1,2,3], expected: [1,2]",
			"object did not match expectation, actual: [1,2,3], expected: [3,2,1]",
			"object did not match expectation, actual: [1,2,3], expected: [1,2,'x']",
			"object did not match expectation, actual: [1,2,3,4,5,6,7], expected: [1,2,'x',4,5,6,7]",
			"object did not match expectation, actual: [1,2,3,4,5,6,7], expected: [1,2,'3',4,5,6,7]",
			"object did not match expectation, actual: {a:'a',b:'b',c:'c'}, expected: {a:'a',b:'b'}",
			"object did not match expectation, actual: {a:3,fn:function(),b:{x:'x',y:'y'}}, expected: {b:{x:'x',y:'zzzzz'},a:3}",
			"object did not match expectation, actual: 3, expected: is.instanceOf(Array)"
		];
		assertFailMessages(expected);
	});	
	
	
	  
	  
	test("multiplicity", function() {
		var myobj = {fn:function(){}};
		var mock = new jqMock.Mock(myobj, "fn");
	
		mock.modify().args('x');
		mock.modify().args('y');
		myobj.fn('x');
		myobj.fn('x');
		myobj.fn('y');
		mock.verifyAll();
		mock.reset();
		
		mock.modify().args({a:'alice',b:'bob'});
		mock.modify().args("foobar").multiplicity(expects.atLeast(3));
		mock.modify().args("foo").multiplicity(expects.atMost(1));
		mock.modify().args('a','b', 'c').multiplicity(expects.times(2,3));
		mock.modify().args(123).multiplicity(0);
		
		for (var i=0; i<2; i++) myobj.fn({a:'alice',b:'bob'});
		for (var i=0; i<2; i++) myobj.fn('foobar');
		for (var i=0; i<2; i++) myobj.fn('foo');
		for (var i=0; i<4; i++) myobj.fn('a','b', 'c');
		myobj.fn(123);
		mock.verifyAll();
		mock.reset();
			
	});
	assertFailure(function() {
		var expected = [];
		expected[0] = noex+
			"fn('x')" + br;
		expected[1] = unos +
			s3 + "1 (1) fn({a:'alice',b:'bob'})" + br +
			"&rarr;2 (3..*) fn('foobar')" + br+
			s3 + "1 (0..1) fn('foo')" + br +
			s3 + "3 (2..3) fn('a','b','c')" + br +
			"&rarr;1 (0..0) fn(123)" + br +
			noex+
			"fn({a:'alice',b:'bob'})" + br+
			"fn('foo')" + br +		
			"fn('a','b','c')" + br;
		assertFailMessages(expected);
	});
	
	
	test("expectThatExceptionThrown", function() {
		jqMock.expectThatExceptionThrown(function() {
			//noop
		}, "something");
		
		jqMock.expectThatExceptionThrown(function() {
			throw "foo";
		}, "bar");
		
		jqMock.expectThatExceptionThrown(function() {
			throw new Error("bad exception 2");
		}, is.exception("bad exception 3"));
		
		jqMock.expectThatExceptionThrown(function() {
			eval("blah");
		}, is.exception({type: RangeError}));
		
		// just that some expression is thrown
		jqMock.expectThatExceptionThrown(function() {
			//noop
		}, is.exception());		
	});
	assertFailure(function() {
		var expected = [
			"The expected exception was never thrown. Expression was: 'something'",
			"expected exception matching 'bar' to be thrown: object did not match expectation, actual: 'foo', expected: 'bar'",
			"expected exception matching is.exception({message:'bad exception 3'}) to be thrown: object did not match expectation, actual: {name:'Error',message:'bad exception 2'}, expected: is.exception({message:'bad exception 3'})",
			"expected exception matching is.exception({type:is.instanceOf(RangeError)}) to be thrown: object did not match expectation, actual: {name:'ReferenceError',message:'blah is not defined'}, expected: is.exception({type:is.instanceOf(RangeError)})",
			"The expected exception was never thrown. Expression was: is.exception()"
		];
		assertFailMessages(expected);
	});


});
