mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
git-subtree-dir: lib/Rhino-Require git-subtree-split: b34307ac1534b153df376820a115efa3be76b092
378 lines
11 KiB
JavaScript
378 lines
11 KiB
JavaScript
/*
|
|
* JSMock 1.2.2, a mock object library for JavaScript
|
|
* Copyright (C) 2006 Justin DeWind
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
JSMock = {
|
|
extend: function(object) {
|
|
var mockControl = new MockControl();
|
|
object.createMock = function(objectToMock) {return mockControl.createMock(objectToMock)};
|
|
object.resetMocks = function() {mockControl.reset()};
|
|
object.verifyMocks = function() {mockControl.verify()};
|
|
|
|
if(!object.tearDown) {
|
|
object.tearDown = function() {
|
|
object.verifyMocks();
|
|
}
|
|
}
|
|
else if(object.tearDown.constructor == Function) {
|
|
object.__oldTearDown__ = object.tearDown;
|
|
object.tearDown = function() {
|
|
object.__oldTearDown__();
|
|
object.verifyMocks();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function MockControl() {
|
|
this.__expectationMatcher = new ExpectationMatcher();
|
|
this.__lastMock = null;
|
|
this.__lastCallName = null;
|
|
}
|
|
|
|
MockControl.prototype = {
|
|
|
|
createMock: function(objectToMock) {
|
|
var mock = { calls: [], expects: function() {this.__recording = true; return this}, __recording: false};
|
|
mock.expect = mock.expects;
|
|
|
|
if(objectToMock != null) {
|
|
|
|
if( typeof(objectToMock) == 'function' ) {
|
|
this.__createMethods(objectToMock, mock);
|
|
this.__createMethods(new objectToMock(), mock);
|
|
}
|
|
else if( typeof(objectToMock) == 'object') {
|
|
this.__createMethods(objectToMock, mock);
|
|
}
|
|
else {
|
|
throw new Error("Cannot mock out a " + typeof(objectToMock));
|
|
}
|
|
|
|
}
|
|
|
|
var self = this;
|
|
mock.addMockMethod = function(method) { self.__createMethod(self, mock, method); }
|
|
|
|
return mock;
|
|
},
|
|
|
|
andReturn: function(returnValue) {
|
|
this.__verifyLastMockNotNull("Cannot set return value without an expectation");
|
|
this.__initializeReturnExpectationForMock();
|
|
this.__lastMock.calls[this.__lastCallName].push( function() { return returnValue; });
|
|
},
|
|
|
|
andThrow: function(throwMsg) {
|
|
this.__verifyLastMockNotNull("Cannot throw error without an expectation");
|
|
this.__initializeReturnExpectationForMock();
|
|
this.__lastMock.calls[this.__lastCallName].push( function() { throw new Error(throwMsg); });
|
|
},
|
|
|
|
andStub: function(block) {
|
|
this.__verifyLastMockNotNull("Cannot stub without an expectation");
|
|
if( typeof(block) != 'function') {
|
|
throw new Error("Stub must be a function");
|
|
}
|
|
this.__initializeReturnExpectationForMock();
|
|
this.__lastMock.calls[this.__lastCallName].push( function() { return block.apply(this, arguments); });
|
|
},
|
|
|
|
reset: function() {
|
|
this.__expectationMatcher.reset();
|
|
},
|
|
|
|
verify: function() {
|
|
if(!this.__expectationMatcher.matches())
|
|
{
|
|
discrepancy = this.__expectationMatcher.discrepancy();
|
|
message = discrepancy.message;
|
|
method = discrepancy.behavior.method
|
|
formattedArgs = ArgumentFormatter.format(discrepancy.behavior.methodArguments);
|
|
this.__expectationMatcher.reset();
|
|
throw new Error(message + ": " + method + "(" + formattedArgs + ")");
|
|
}
|
|
else {
|
|
this.__expectationMatcher.reset();
|
|
}
|
|
|
|
},
|
|
|
|
__createMethods: function(object, mock) {
|
|
for( property in object ) {
|
|
if( this.__isPublicMethod(object, property) ) {
|
|
this.__createMethod( this, mock, property );
|
|
}
|
|
}
|
|
},
|
|
|
|
__createMethod: function(control, mock, method) {
|
|
mock[method] =
|
|
function() {
|
|
if( mock.__recording ) {
|
|
control.__lastMock = mock;
|
|
control.__lastCallName = method;
|
|
control.__expectationMatcher.addExpectedMethodCall( mock, method, arguments );
|
|
mock.__recording = false;
|
|
return control;
|
|
}
|
|
else {
|
|
control.__expectationMatcher.addActualMethodCall( mock, method, arguments );
|
|
if( mock.calls[method] != null) {
|
|
returnValue = mock.calls[method].shift();
|
|
if( typeof(returnValue) == 'function') {
|
|
return returnValue.apply(this, arguments);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
__isPublicMethod: function(object, property) {
|
|
try {
|
|
return typeof(object[property]) == 'function' && property.charAt(0) != "_";
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
__verifyLastMockNotNull: function(throwMsg) {
|
|
if(this.__lastMock == null) {
|
|
throw new Error(throwMsg);
|
|
}
|
|
},
|
|
|
|
__initializeReturnExpectationForMock: function() {
|
|
if(typeof(this.__lastMock.calls[this.__lastCallName]) == 'undefined') {
|
|
this.__lastMock.calls[this.__lastCallName] = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
function ExpectationMatcher() {
|
|
this.__expectationBehaviorList = [];
|
|
this.__actualBehaviorList = [];
|
|
this.__discrepancy = null;
|
|
|
|
}
|
|
|
|
ExpectationMatcher.prototype = {
|
|
addExpectedMethodCall: function(caller, method, methodArguments ) {
|
|
this.__expectationBehaviorList.push(new InvocationBehavior(caller, method, methodArguments));
|
|
},
|
|
|
|
addActualMethodCall: function(caller, method, methodArguments ) {
|
|
this.__actualBehaviorList.push(new InvocationBehavior(caller, method, methodArguments));
|
|
},
|
|
|
|
matches: function() {
|
|
var self = this;
|
|
var matches = true;
|
|
|
|
this.__expectationBehaviorList.eachIndexForJsMock(function(index, expectedBehavior) {
|
|
var actualBehavior = (self.__actualBehaviorList.length > index) ? self.__actualBehaviorList[index] : null;
|
|
|
|
if(matches) {
|
|
if( actualBehavior === null ) {
|
|
self.__discrepancy = new Discrepancy("Expected function not called", expectedBehavior);
|
|
matches = false;
|
|
}
|
|
else if( expectedBehavior.method != actualBehavior.method ) {
|
|
self.__discrepancy = new Discrepancy("Surprise call", actualBehavior);
|
|
matches = false;
|
|
}
|
|
else if( expectedBehavior.caller != actualBehavior.caller ) {
|
|
self.__discrepancy = new Discrepancy("Surprise call from unexpected caller", actualBehavior);
|
|
matches = false;
|
|
}
|
|
else if( !self.__matchArguments(expectedBehavior.methodArguments, actualBehavior.methodArguments) ) {
|
|
self.__discrepancy = new Discrepancy("Unexpected Arguments", actualBehavior);
|
|
matches = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
if( this.__actualBehaviorList.length > this.__expectationBehaviorList.length && matches ) {
|
|
this.__discrepancy = new Discrepancy("Surprise call", this.__actualBehaviorList[this.__expectationBehaviorList.length]);
|
|
matches = false
|
|
}
|
|
|
|
return matches;
|
|
},
|
|
|
|
reset: function() {
|
|
this.__expectationBehaviorList = [];
|
|
this.__actualBehaviorList = [];
|
|
this.__discrepancy = null;
|
|
},
|
|
|
|
discrepancy: function() {
|
|
return this.__discrepancy;
|
|
},
|
|
|
|
__matchArguments: function(expectedArgs, actualArgs) {
|
|
var expectedArray = this.__convertArgumentsToArray(expectedArgs);
|
|
var actualArray = this.__convertArgumentsToArray(actualArgs);
|
|
return ArgumentMatcher.matches(expectedArray, actualArray);
|
|
},
|
|
|
|
__convertArgumentsToArray: function(args) {
|
|
var convertedArguments = [];
|
|
|
|
for(var i = 0; i < args.length; i++) {
|
|
convertedArguments[i] = args[i];
|
|
}
|
|
|
|
return convertedArguments;
|
|
}
|
|
}
|
|
|
|
function InvocationBehavior(caller, method, methodArguments) {
|
|
this.caller = caller;
|
|
this.method = method;
|
|
this.methodArguments = methodArguments;
|
|
}
|
|
|
|
function TypeOf(type) {
|
|
if(typeof(type) != 'function')
|
|
throw new Error("Can only take constructors");
|
|
|
|
this.type = type;
|
|
}
|
|
|
|
TypeOf.isA = function(type) { return new TypeOf(type); };
|
|
|
|
ArgumentMatcher = {
|
|
|
|
matches: function(expected, actual) {
|
|
return this.__delegateMatching(expected, actual);
|
|
},
|
|
|
|
__delegateMatching: function(expected, actual) {
|
|
if( expected == null ) {
|
|
return this.__match( expected, actual );
|
|
}
|
|
else if( expected.constructor == TypeOf ) {
|
|
return this.__match(expected.type, actual.constructor);
|
|
}
|
|
else if( expected.constructor == Array ) {
|
|
return this.__matchArrays(expected, actual);
|
|
}
|
|
else {
|
|
return this.__match(expected, actual);
|
|
}
|
|
},
|
|
|
|
__match: function(expected, actual) {
|
|
return ( expected == actual );
|
|
},
|
|
|
|
__matchArrays: function(expected, actual) {
|
|
if ( actual == null)
|
|
return false;
|
|
|
|
if( actual.constructor != Array)
|
|
return false;
|
|
|
|
if( expected.length != actual.length )
|
|
return false;
|
|
|
|
for(var i = 0; i < expected.length; i++ ) {
|
|
if( !this.__delegateMatching(expected[i], actual[i]) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function Discrepancy(message, behavior) {
|
|
if(behavior.constructor != InvocationBehavior)
|
|
throw new Error("The behavior can only be an InvocationBehavior object");
|
|
|
|
this.message = message;
|
|
this.behavior = behavior;
|
|
}
|
|
|
|
ArgumentFormatter = {
|
|
|
|
format: function(args) {
|
|
var formattedArgs = "";
|
|
for(var i = 0; i < args.length; i++) {
|
|
if( args[i] == null ) {
|
|
formattedArgs += ( formattedArgs == "" ) ? "null" : ", " + "null";
|
|
}
|
|
else if( args[i].constructor == TypeOf || args[i].constructor == Function) {
|
|
var func = ( args[i].constructor == TypeOf ) ? args[i].type : args[i];
|
|
formattedArgs += ( formattedArgs == "" ) ? this.__formatFunction(func) : ", " + this.__formatFunction(func);
|
|
}
|
|
else if( typeof(args[i]) == "string" ) {
|
|
formattedArgs += ( formattedArgs == "" ) ? "\"" + args[i].toString() + "\"" : ", \"" + args[i].toString() + "\""
|
|
}
|
|
else if( args[i].constructor == Array ) {
|
|
formattedArgs += ( formattedArgs == "" ) ? "[" + this.format(args[i]) + "]" : ", [" + this.format(args[i]) + "]";
|
|
}
|
|
else {
|
|
formattedArgs += ( formattedArgs == "" ) ? args[i].toString() : ", " + args[i].toString();
|
|
}
|
|
}
|
|
return formattedArgs;
|
|
},
|
|
|
|
__formatFunction: function(func) {
|
|
// Manual checking is done for internal/native functions
|
|
// since Safari will not display them correctly
|
|
// for the intended regex parsing.
|
|
|
|
if(func == Array) {
|
|
return "Array";
|
|
} else if(func == Date) {
|
|
return "Date";
|
|
} else if(func == Object) {
|
|
return "Object";
|
|
} else if(func == String) {
|
|
return "String";
|
|
} else if(func == Function) {
|
|
return "Function";
|
|
} else if(func == RegExp) {
|
|
return "RegExp";
|
|
} else if(func == Error) {
|
|
return "Error";
|
|
} else if(func == Number) {
|
|
return "Number";
|
|
} else if(func == Boolean) {
|
|
return "Boolean";
|
|
}
|
|
var formattedFunc = func.toString().match(/function (\w+)/);
|
|
|
|
return ( formattedFunc == null ) ? "{{Closure}}" : formattedFunc[1];
|
|
}
|
|
|
|
}
|
|
|
|
/* Helpers */
|
|
|
|
// Implemented each method with a unique name to avoid conflicting
|
|
// with other libraries that implement it.
|
|
Array.prototype.eachIndexForJsMock = function(block) {
|
|
for(var index = 0; index < this.length; index++)
|
|
{
|
|
block(index, this[index]);
|
|
}
|
|
}
|