summaryrefslogtreecommitdiffstats
path: root/chrome/third_party
diff options
context:
space:
mode:
authorscr@chromium.org <scr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-12 00:17:36 +0000
committerscr@chromium.org <scr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-12 00:17:36 +0000
commitbf8feaad4973013b788d5cd0a57ae976edc22468 (patch)
tree00ccf8b625486b3bf05c447f16bf4171c57ae673 /chrome/third_party
parent839f4172be712f8ba62cdcb29cc3301ee2c7f3f9 (diff)
downloadchromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.zip
chromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.tar.gz
chromium_src-bf8feaad4973013b788d5cd0a57ae976edc22468.tar.bz2
Added options browser_tests using the generator and js handler framework.
This patch turned out to be fairly large. Let me describe the ultimate goal: - To write WebUI tests in javascript, with as little C++ as possible for the simple case, yet powerful enough to support more complicated cases. options.js illustrates the simple case, and print_preview.js illustrates the complicated case. Original changes: - Refactored test_tab_strip_observer into test_navigation_observer so that it could be used by itself without needing the TabInsertedAt logic, which PrintPreview needs when there's no TabContentsWrapper available. - Added assertEqualsArray for comparing two arrays shallowly (javascript == fails). - Provided logic in WebUIBrowserTest and in the javascript2webui.js generation script to allow browsing to a url with preload of injected javascript. - Corrected test_navigation_observer to wait for the right notification before calling callback (which runs after the page's javascript is loaded but before its onload). - Added guts to define OS_* ifdefs for javascript to test for disabling tests. - Moved the handler from settings_browsertest.cc to settings.js - use __proto__ when overriding chrome to allow other members to be seen (commandLineString, e.g.) Additions made during review: - Switched to generative mechanism: TEST_F, GEN, which output during generation, and register during runtime. - JS fixtures provide configuration members - Add configuration hooks to generate in C++ test function - Output directly to .cc file rather than needing hand-made .cc file which includes the generated file. - Changed preload to take testFixture and testName. - include and use mock4js to ensure handler methods are called. - auto-generate the typedef WebUIBrowserTest testFixture unless overridden. R=jhawkins@chromium.org,dtseng@chromium.org BUG=None TEST=browser_tests --gtest_filter=SettingsWebUITest.* Review URL: http://codereview.chromium.org/7237030 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@92084 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/third_party')
-rw-r--r--chrome/third_party/mock4js/LICENSE19
-rw-r--r--chrome/third_party/mock4js/README.chromium8
-rw-r--r--chrome/third_party/mock4js/examples/PriceService.js43
-rw-r--r--chrome/third_party/mock4js/examples/PriceServiceTest.html60
-rw-r--r--chrome/third_party/mock4js/examples/Publisher.js29
-rw-r--r--chrome/third_party/mock4js/examples/PublisherTest.html47
-rw-r--r--chrome/third_party/mock4js/mock4js.js625
7 files changed, 831 insertions, 0 deletions
diff --git a/chrome/third_party/mock4js/LICENSE b/chrome/third_party/mock4js/LICENSE
new file mode 100644
index 0000000..cb92f08
--- /dev/null
+++ b/chrome/third_party/mock4js/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2009 by Tung Mac.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/chrome/third_party/mock4js/README.chromium b/chrome/third_party/mock4js/README.chromium
new file mode 100644
index 0000000..42696a1
--- /dev/null
+++ b/chrome/third_party/mock4js/README.chromium
@@ -0,0 +1,8 @@
+Name: mock4js
+Short Name: mock4js
+URL: http://mock4js.sourceforge.net/
+Version: 0.2
+
+Mock4JS is a dynamic-mock library for Javascript. It enables developers to write
+tests that exercise and verify the interactions between the objects in the
+application. Its syntax is very expressive and is closely based on jMock 1.
diff --git a/chrome/third_party/mock4js/examples/PriceService.js b/chrome/third_party/mock4js/examples/PriceService.js
new file mode 100644
index 0000000..f20aa93
--- /dev/null
+++ b/chrome/third_party/mock4js/examples/PriceService.js
@@ -0,0 +1,43 @@
+/**
+ * PriceCache
+ */
+function PriceCache() {
+}
+
+PriceCache.prototype = {
+ getCachedPrice: function(instrumentId) {
+ },
+ setCachedPrice: function(instrumentId, price) {
+ }
+}
+
+/**
+ * PriceFetcher
+ */
+function PriceFetcher() {
+}
+
+PriceFetcher.prototype = {
+ getPriceFromServer: function(instrumentId) {
+ }
+}
+
+
+/**
+ * PriceService
+ */
+function PriceService(priceFetcher, priceCache) {
+ this._priceFetcher = priceFetcher;
+ this._priceCache = priceCache;
+}
+
+PriceService.prototype = {
+ getPrice: function(instrumentId) {
+ var price = this._priceCache.getCachedPrice(instrumentId);
+ if(price==null) {
+ price = this._priceFetcher.getPriceFromServer(instrumentId);
+ this._priceCache.setCachedPrice(instrumentId, price);
+ }
+ return price;
+ }
+}
diff --git a/chrome/third_party/mock4js/examples/PriceServiceTest.html b/chrome/third_party/mock4js/examples/PriceServiceTest.html
new file mode 100644
index 0000000..a535aef
--- /dev/null
+++ b/chrome/third_party/mock4js/examples/PriceServiceTest.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+"http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Tests</title>
+ <link rel="stylesheet" type="text/css" href="jsunit/css/jsUnitStyle.css">
+ <script language="JavaScript" type="text/javascript" src="../../jsunit/app/jsUnitCore.js"></script>
+ <script language="JavaScript" type="text/javascript" src="../mock4js.js"></script>
+ <script language="JavaScript" type="text/javascript" src="PriceService.js"></script>
+ <script language="JavaScript" type="text/javascript">
+
+ Mock4JS.addMockSupport(this);
+
+ var mockPriceFetcher;
+ var mockPriceCache;
+ var priceService;
+
+ function setUp() {
+ Mock4JS.clearMocksToVerify();
+ mockPriceFetcher = mock(PriceFetcher);
+ mockPriceCache = mock(PriceCache);
+ priceService = new PriceService(mockPriceFetcher.proxy(), mockPriceCache.proxy());
+ }
+
+ function tearDown() {
+ Mock4JS.verifyAllMocks();
+ }
+
+ function testGetsPriceFromFetcherWhenPriceNotInCache() {
+ mockPriceCache.expects(once()).getCachedPrice("USDGBP").will(returnValue(null));
+ mockPriceFetcher.expects(once()).getPriceFromServer("USDGBP").will(returnValue(123.4));
+ mockPriceCache.expects(once()).setCachedPrice("USDGBP", 123.4);
+
+ var result = priceService.getPrice("USDGBP");
+
+ assertEquals("Should have returned price from server", 123.4, result);
+ }
+
+ function testDoesntGetsPriceFromFetcherWhenPriceAlreadyInCache() {
+ mockPriceCache.expects(once()).getCachedPrice("USDGBP").will(returnValue(123.4));
+ mockPriceCache.expects(never()).setCachedPrice();
+ mockPriceFetcher.expects(never()).getPriceFromServer("USDGBP");
+
+ var result = priceService.getPrice("USDGBP");
+
+ assertEquals("Should have returned price from cache", 123.4, result);
+ }
+
+ </script>
+ </head>
+
+ <body>
+ <h1>JsUnit Tests</h1>
+
+ <p>This page contains some JsUnit tests. To see them, take a look at the source.</p>
+ </body>
+</html>
+
diff --git a/chrome/third_party/mock4js/examples/Publisher.js b/chrome/third_party/mock4js/examples/Publisher.js
new file mode 100644
index 0000000..7405a06
--- /dev/null
+++ b/chrome/third_party/mock4js/examples/Publisher.js
@@ -0,0 +1,29 @@
+/**
+ * Subscriber
+ */
+function Subscriber() {
+}
+
+Subscriber.prototype = {
+ receive: function(message) {
+ }
+}
+
+/**
+ * Publisher
+ */
+function Publisher() {
+ this._subscribers = [];
+}
+
+Publisher.prototype = {
+ publish: function(message) {
+ for(var i=0; i<this._subscribers.length; i++) {
+ var subscriber = this._subscribers[i];
+ subscriber.receive(message);
+ }
+ },
+ add: function(subscriber) {
+ this._subscribers.push(subscriber);
+ }
+} \ No newline at end of file
diff --git a/chrome/third_party/mock4js/examples/PublisherTest.html b/chrome/third_party/mock4js/examples/PublisherTest.html
new file mode 100644
index 0000000..6bda220
--- /dev/null
+++ b/chrome/third_party/mock4js/examples/PublisherTest.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+"http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Tests</title>
+ <link rel="stylesheet" type="text/css" href="jsunit/css/jsUnitStyle.css">
+ <script language="JavaScript" type="text/javascript" src="../../jsunit/app/jsUnitCore.js"></script>
+ <script language="JavaScript" type="text/javascript" src="..//mock4js.js"></script>
+ <script language="JavaScript" type="text/javascript" src="Publisher.js"></script>
+ <script language="JavaScript" type="text/javascript">
+
+ Mock4JS.addMockSupport(this);
+
+ function setUp() {
+ Mock4JS.clearMocksToVerify();
+ }
+
+ function tearDown() {
+ Mock4JS.verifyAllMocks();
+ }
+
+ function testOneSubscriberReceivesAMessage() {
+ // setup
+ var mockSubscriber = mock(Subscriber);
+ var publisher = new Publisher();
+ publisher.add(mockSubscriber.proxy());
+
+ var message = "message";
+
+ // expectations
+ mockSubscriber.expects(once()).receive(message);
+
+ // execute
+ publisher.publish(message);
+ }
+ </script>
+ </head>
+
+ <body>
+ <h1>JsUnit Tests</h1>
+
+ <p>This page contains some JsUnit tests. To see them, take a look at the source.</p>
+ </body>
+</html>
+
diff --git a/chrome/third_party/mock4js/mock4js.js b/chrome/third_party/mock4js/mock4js.js
new file mode 100644
index 0000000..933e0a6
--- /dev/null
+++ b/chrome/third_party/mock4js/mock4js.js
@@ -0,0 +1,625 @@
+/**
+ * Mock4JS 0.2
+ * http://mock4js.sourceforge.net/
+ */
+
+Mock4JS = {
+ _mocksToVerify: [],
+ _convertToConstraint: function(constraintOrValue) {
+ if(constraintOrValue.argumentMatches) {
+ return constraintOrValue; // it's already an ArgumentMatcher
+ } else {
+ return new MatchExactly(constraintOrValue); // default to eq(...)
+ }
+ },
+ addMockSupport: function(object) {
+ // mock creation
+ object.mock = function(mockedType) {
+ if(!mockedType) {
+ throw new Mock4JSException("Cannot create mock: type to mock cannot be found or is null");
+ }
+ var newMock = new Mock(mockedType);
+ Mock4JS._mocksToVerify.push(newMock);
+ return newMock;
+ }
+
+ // syntactic sugar for expects()
+ object.once = function() {
+ return new CallCounter(1);
+ }
+ object.never = function() {
+ return new CallCounter(0);
+ }
+ object.exactly = function(expectedCallCount) {
+ return new CallCounter(expectedCallCount);
+ }
+ object.atLeastOnce = function() {
+ return new InvokeAtLeastOnce();
+ }
+
+ // syntactic sugar for argument expectations
+ object.ANYTHING = new MatchAnything();
+ object.NOT_NULL = new MatchAnythingBut(new MatchExactly(null));
+ object.NOT_UNDEFINED = new MatchAnythingBut(new MatchExactly(undefined));
+ object.eq = function(expectedValue) {
+ return new MatchExactly(expectedValue);
+ }
+ object.not = function(valueNotExpected) {
+ var argConstraint = Mock4JS._convertToConstraint(valueNotExpected);
+ return new MatchAnythingBut(argConstraint);
+ }
+ object.and = function() {
+ var constraints = [];
+ for(var i=0; i<arguments.length; i++) {
+ constraints[i] = Mock4JS._convertToConstraint(arguments[i]);
+ }
+ return new MatchAllOf(constraints);
+ }
+ object.or = function() {
+ var constraints = [];
+ for(var i=0; i<arguments.length; i++) {
+ constraints[i] = Mock4JS._convertToConstraint(arguments[i]);
+ }
+ return new MatchAnyOf(constraints);
+ }
+ object.stringContains = function(substring) {
+ return new MatchStringContaining(substring);
+ }
+
+ // syntactic sugar for will()
+ object.returnValue = function(value) {
+ return new ReturnValueAction(value);
+ }
+ object.throwException = function(exception) {
+ return new ThrowExceptionAction(exception);
+ }
+ },
+ clearMocksToVerify: function() {
+ Mock4JS._mocksToVerify = [];
+ },
+ verifyAllMocks: function() {
+ for(var i=0; i<Mock4JS._mocksToVerify.length; i++) {
+ Mock4JS._mocksToVerify[i].verify();
+ }
+ }
+}
+
+Mock4JSUtil = {
+ hasFunction: function(obj, methodName) {
+ return typeof obj == 'object' && typeof obj[methodName] == 'function';
+ },
+ join: function(list) {
+ var result = "";
+ for(var i=0; i<list.length; i++) {
+ var item = list[i];
+ if(Mock4JSUtil.hasFunction(item, "describe")) {
+ result += item.describe();
+ }
+ else if(typeof list[i] == 'string') {
+ result += "\""+list[i]+"\"";
+ } else {
+ result += list[i];
+ }
+
+ if(i<list.length-1) result += ", ";
+ }
+ return result;
+ }
+}
+
+Mock4JSException = function(message) {
+ this.message = message;
+}
+
+Mock4JSException.prototype = {
+ toString: function() {
+ return this.message;
+ }
+}
+
+/**
+ * Assert function that makes use of the constraint methods
+ */
+assertThat = function(expected, argumentMatcher) {
+ if(!argumentMatcher.argumentMatches(expected)) {
+ fail("Expected '"+expected+"' to be "+argumentMatcher.describe());
+ }
+}
+
+/**
+ * CallCounter
+ */
+function CallCounter(expectedCount) {
+ this._expectedCallCount = expectedCount;
+ this._actualCallCount = 0;
+}
+
+CallCounter.prototype = {
+ addActualCall: function() {
+ this._actualCallCount++;
+ if(this._actualCallCount > this._expectedCallCount) {
+ throw new Mock4JSException("unexpected invocation");
+ }
+ },
+
+ verify: function() {
+ if(this._actualCallCount < this._expectedCallCount) {
+ throw new Mock4JSException("expected method was not invoked the expected number of times");
+ }
+ },
+
+ describe: function() {
+ if(this._expectedCallCount == 0) {
+ return "not expected";
+ } else if(this._expectedCallCount == 1) {
+ var msg = "expected once";
+ if(this._actualCallCount >= 1) {
+ msg += " and has been invoked";
+ }
+ return msg;
+ } else {
+ var msg = "expected "+this._expectedCallCount+" times";
+ if(this._actualCallCount > 0) {
+ msg += ", invoked "+this._actualCallCount + " times";
+ }
+ return msg;
+ }
+ }
+}
+
+function InvokeAtLeastOnce() {
+ this._hasBeenInvoked = false;
+}
+
+InvokeAtLeastOnce.prototype = {
+ addActualCall: function() {
+ this._hasBeenInvoked = true;
+ },
+
+ verify: function() {
+ if(this._hasBeenInvoked === false) {
+ throw new Mock4JSException(describe());
+ }
+ },
+
+ describe: function() {
+ var desc = "expected at least once";
+ if(this._hasBeenInvoked) desc+=" and has been invoked";
+ return desc;
+ }
+}
+
+/**
+ * ArgumentMatchers
+ */
+
+function MatchExactly(expectedValue) {
+ this._expectedValue = expectedValue;
+}
+
+MatchExactly.prototype = {
+ argumentMatches: function(actualArgument) {
+ if(this._expectedValue instanceof Array) {
+ if(!(actualArgument instanceof Array)) return false;
+ if(this._expectedValue.length != actualArgument.length) return false;
+ for(var i=0; i<this._expectedValue.length; i++) {
+ if(this._expectedValue[i] != actualArgument[i]) return false;
+ }
+ return true;
+ } else {
+ return this._expectedValue == actualArgument;
+ }
+ },
+ describe: function() {
+ if(typeof this._expectedValue == "string") {
+ return "eq(\""+this._expectedValue+"\")";
+ } else {
+ return "eq("+this._expectedValue+")";
+ }
+ }
+}
+
+function MatchAnything() {
+}
+
+MatchAnything.prototype = {
+ argumentMatches: function(actualArgument) {
+ return true;
+ },
+ describe: function() {
+ return "ANYTHING";
+ }
+}
+
+function MatchAnythingBut(matcherToNotMatch) {
+ this._matcherToNotMatch = matcherToNotMatch;
+}
+
+MatchAnythingBut.prototype = {
+ argumentMatches: function(actualArgument) {
+ return !this._matcherToNotMatch.argumentMatches(actualArgument);
+ },
+ describe: function() {
+ return "not("+this._matcherToNotMatch.describe()+")";
+ }
+}
+
+function MatchAllOf(constraints) {
+ this._constraints = constraints;
+}
+
+
+MatchAllOf.prototype = {
+ argumentMatches: function(actualArgument) {
+ for(var i=0; i<this._constraints.length; i++) {
+ var constraint = this._constraints[i];
+ if(!constraint.argumentMatches(actualArgument)) return false;
+ }
+ return true;
+ },
+ describe: function() {
+ return "and("+Mock4JSUtil.join(this._constraints)+")";
+ }
+}
+
+function MatchAnyOf(constraints) {
+ this._constraints = constraints;
+}
+
+MatchAnyOf.prototype = {
+ argumentMatches: function(actualArgument) {
+ for(var i=0; i<this._constraints.length; i++) {
+ var constraint = this._constraints[i];
+ if(constraint.argumentMatches(actualArgument)) return true;
+ }
+ return false;
+ },
+ describe: function() {
+ return "or("+Mock4JSUtil.join(this._constraints)+")";
+ }
+}
+
+
+function MatchStringContaining(stringToLookFor) {
+ this._stringToLookFor = stringToLookFor;
+}
+
+MatchStringContaining.prototype = {
+ argumentMatches: function(actualArgument) {
+ if(typeof actualArgument != 'string') throw new Mock4JSException("stringContains() must be given a string, actually got a "+(typeof actualArgument));
+ return (actualArgument.indexOf(this._stringToLookFor) != -1);
+ },
+ describe: function() {
+ return "a string containing \""+this._stringToLookFor+"\"";
+ }
+}
+
+
+/**
+ * StubInvocation
+ */
+function StubInvocation(expectedMethodName, expectedArgs, actionSequence) {
+ this._expectedMethodName = expectedMethodName;
+ this._expectedArgs = expectedArgs;
+ this._actionSequence = actionSequence;
+}
+
+StubInvocation.prototype = {
+ matches: function(invokedMethodName, invokedMethodArgs) {
+ if (invokedMethodName != this._expectedMethodName) {
+ return false;
+ }
+
+ if (invokedMethodArgs.length != this._expectedArgs.length) {
+ return false;
+ }
+
+ for(var i=0; i<invokedMethodArgs.length; i++) {
+ var expectedArg = this._expectedArgs[i];
+ var invokedArg = invokedMethodArgs[i];
+ if(!expectedArg.argumentMatches(invokedArg)) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ invoked: function() {
+ try {
+ return this._actionSequence.invokeNextAction();
+ } catch(e) {
+ if(e instanceof Mock4JSException) {
+ throw new Mock4JSException(this.describeInvocationNameAndArgs()+" - "+e.message);
+ } else {
+ throw e;
+ }
+ }
+ },
+
+ will: function() {
+ this._actionSequence.addAll.apply(this._actionSequence, arguments);
+ },
+
+ describeInvocationNameAndArgs: function() {
+ return this._expectedMethodName+"("+Mock4JSUtil.join(this._expectedArgs)+")";
+ },
+
+ describe: function() {
+ return "stub: "+this.describeInvocationNameAndArgs();
+ },
+
+ verify: function() {
+ }
+}
+
+/**
+ * ExpectedInvocation
+ */
+function ExpectedInvocation(expectedMethodName, expectedArgs, expectedCallCounter) {
+ this._stubInvocation = new StubInvocation(expectedMethodName, expectedArgs, new ActionSequence());
+ this._expectedCallCounter = expectedCallCounter;
+}
+
+ExpectedInvocation.prototype = {
+ matches: function(invokedMethodName, invokedMethodArgs) {
+ try {
+ return this._stubInvocation.matches(invokedMethodName, invokedMethodArgs);
+ } catch(e) {
+ throw new Mock4JSException("method "+this._stubInvocation.describeInvocationNameAndArgs()+": "+e.message);
+ }
+ },
+
+ invoked: function() {
+ try {
+ this._expectedCallCounter.addActualCall();
+ } catch(e) {
+ throw new Mock4JSException(e.message+": "+this._stubInvocation.describeInvocationNameAndArgs());
+ }
+ return this._stubInvocation.invoked();
+ },
+
+ will: function() {
+ this._stubInvocation.will.apply(this._stubInvocation, arguments);
+ },
+
+ describe: function() {
+ return this._expectedCallCounter.describe()+": "+this._stubInvocation.describeInvocationNameAndArgs();
+ },
+
+ verify: function() {
+ try {
+ this._expectedCallCounter.verify();
+ } catch(e) {
+ throw new Mock4JSException(e.message+": "+this._stubInvocation.describeInvocationNameAndArgs());
+ }
+ }
+}
+
+/**
+ * MethodActions
+ */
+function ReturnValueAction(valueToReturn) {
+ this._valueToReturn = valueToReturn;
+}
+
+ReturnValueAction.prototype = {
+ invoke: function() {
+ return this._valueToReturn;
+ },
+ describe: function() {
+ return "returns "+this._valueToReturn;
+ }
+}
+
+function ThrowExceptionAction(exceptionToThrow) {
+ this._exceptionToThrow = exceptionToThrow;
+}
+
+ThrowExceptionAction.prototype = {
+ invoke: function() {
+ throw this._exceptionToThrow;
+ },
+ describe: function() {
+ return "throws "+this._exceptionToThrow;
+ }
+}
+
+function ActionSequence() {
+ this._ACTIONS_NOT_SETUP = "_ACTIONS_NOT_SETUP";
+ this._actionSequence = this._ACTIONS_NOT_SETUP;
+ this._indexOfNextAction = 0;
+}
+
+ActionSequence.prototype = {
+ invokeNextAction: function() {
+ if(this._actionSequence === this._ACTIONS_NOT_SETUP) {
+ return;
+ } else {
+ if(this._indexOfNextAction >= this._actionSequence.length) {
+ throw new Mock4JSException("no more values to return");
+ } else {
+ var action = this._actionSequence[this._indexOfNextAction];
+ this._indexOfNextAction++;
+ return action.invoke();
+ }
+ }
+ },
+
+ addAll: function() {
+ this._actionSequence = [];
+ for(var i=0; i<arguments.length; i++) {
+ if(typeof arguments[i] != 'object' && arguments[i].invoke === undefined) {
+ throw new Error("cannot add a method action that does not have an invoke() method");
+ }
+ this._actionSequence.push(arguments[i]);
+ }
+ }
+}
+
+function StubActionSequence() {
+ this._ACTIONS_NOT_SETUP = "_ACTIONS_NOT_SETUP";
+ this._actionSequence = this._ACTIONS_NOT_SETUP;
+ this._indexOfNextAction = 0;
+}
+
+StubActionSequence.prototype = {
+ invokeNextAction: function() {
+ if(this._actionSequence === this._ACTIONS_NOT_SETUP) {
+ return;
+ } else if(this._actionSequence.length == 1) {
+ // if there is only one method action, keep doing that on every invocation
+ return this._actionSequence[0].invoke();
+ } else {
+ if(this._indexOfNextAction >= this._actionSequence.length) {
+ throw new Mock4JSException("no more values to return");
+ } else {
+ var action = this._actionSequence[this._indexOfNextAction];
+ this._indexOfNextAction++;
+ return action.invoke();
+ }
+ }
+ },
+
+ addAll: function() {
+ this._actionSequence = [];
+ for(var i=0; i<arguments.length; i++) {
+ if(typeof arguments[i] != 'object' && arguments[i].invoke === undefined) {
+ throw new Error("cannot add a method action that does not have an invoke() method");
+ }
+ this._actionSequence.push(arguments[i]);
+ }
+ }
+}
+
+
+/**
+ * Mock
+ */
+function Mock(mockedType) {
+ if(mockedType === undefined || mockedType.prototype === undefined) {
+ throw new Mock4JSException("Unable to create Mock: must create Mock using a class not prototype, eg. 'new Mock(TypeToMock)' or using the convenience method 'mock(TypeToMock)'");
+ }
+ this._mockedType = mockedType.prototype;
+ this._expectedCallCount;
+ this._isRecordingExpectations = false;
+ this._expectedInvocations = [];
+
+ // setup proxy
+ var IntermediateClass = new Function();
+ IntermediateClass.prototype = mockedType.prototype;
+ var ChildClass = new Function();
+ ChildClass.prototype = new IntermediateClass();
+ this._proxy = new ChildClass();
+ this._proxy.mock = this;
+
+ for(property in mockedType.prototype) {
+ if(this._isPublicMethod(mockedType.prototype, property)) {
+ var publicMethodName = property;
+ this._proxy[publicMethodName] = this._createMockedMethod(publicMethodName);
+ this[publicMethodName] = this._createExpectationRecordingMethod(publicMethodName);
+ }
+ }
+}
+
+Mock.prototype = {
+
+ proxy: function() {
+ return this._proxy;
+ },
+
+ expects: function(expectedCallCount) {
+ this._expectedCallCount = expectedCallCount;
+ this._isRecordingExpectations = true;
+ this._isRecordingStubs = false;
+ return this;
+ },
+
+ stubs: function() {
+ this._isRecordingExpectations = false;
+ this._isRecordingStubs = true;
+ return this;
+ },
+
+ verify: function() {
+ for(var i=0; i<this._expectedInvocations.length; i++) {
+ var expectedInvocation = this._expectedInvocations[i];
+ try {
+ expectedInvocation.verify();
+ } catch(e) {
+ var failMsg = e.message+this._describeMockSetup();
+ throw new Mock4JSException(failMsg);
+ }
+ }
+ },
+
+ _isPublicMethod: function(mockedType, property) {
+ try {
+ var isMethod = typeof(mockedType[property]) == 'function';
+ var isPublic = property.charAt(0) != "_";
+ return isMethod && isPublic;
+ } catch(e) {
+ return false;
+ }
+ },
+
+ _createExpectationRecordingMethod: function(methodName) {
+ return function() {
+ // ensure all arguments are instances of ArgumentMatcher
+ var expectedArgs = [];
+ for(var i=0; i<arguments.length; i++) {
+ if(arguments[i] !== null && arguments[i] !== undefined && arguments[i].argumentMatches) {
+ expectedArgs[i] = arguments[i];
+ } else {
+ expectedArgs[i] = new MatchExactly(arguments[i]);
+ }
+ }
+
+ // create stub or expected invocation
+ var expectedInvocation;
+ if(this._isRecordingExpectations) {
+ expectedInvocation = new ExpectedInvocation(methodName, expectedArgs, this._expectedCallCount);
+ } else {
+ expectedInvocation = new StubInvocation(methodName, expectedArgs, new StubActionSequence());
+ }
+
+ this._expectedInvocations.push(expectedInvocation);
+
+ this._isRecordingExpectations = false;
+ this._isRecordingStubs = false;
+ return expectedInvocation;
+ }
+ },
+
+ _createMockedMethod: function(methodName) {
+ return function() {
+ // go through expectation list backwards to ensure later expectations override earlier ones
+ for(var i=this.mock._expectedInvocations.length-1; i>=0; i--) {
+ var expectedInvocation = this.mock._expectedInvocations[i];
+ if(expectedInvocation.matches(methodName, arguments)) {
+ try {
+ return expectedInvocation.invoked();
+ } catch(e) {
+ if(e instanceof Mock4JSException) {
+ throw new Mock4JSException(e.message+this.mock._describeMockSetup());
+ } else {
+ // the user setup the mock to throw a specific error, so don't modify the message
+ throw e;
+ }
+ }
+ }
+ }
+ var failMsg = "unexpected invocation: "+methodName+"("+Mock4JSUtil.join(arguments)+")"+this.mock._describeMockSetup();
+ throw new Mock4JSException(failMsg);
+ };
+ },
+
+ _describeMockSetup: function() {
+ var msg = "\nAllowed:";
+ for(var i=0; i<this._expectedInvocations.length; i++) {
+ var expectedInvocation = this._expectedInvocations[i];
+ msg += "\n" + expectedInvocation.describe();
+ }
+ return msg;
+ }
+} \ No newline at end of file