- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / third_party / mock4js / mock4js.js
1 /**\r
2  * Mock4JS 0.2\r
3  * http://mock4js.sourceforge.net/\r
4  */\r
5 \r
6 Mock4JS = {\r
7         _mocksToVerify: [],\r
8         _convertToConstraint: function(constraintOrValue) {\r
9                 if(constraintOrValue.argumentMatches) {\r
10                         return constraintOrValue; // it's already an ArgumentMatcher\r
11                 } else {\r
12                         return new MatchExactly(constraintOrValue);     // default to eq(...)\r
13                 }\r
14         },\r
15         addMockSupport: function(object) {\r
16                 // mock creation\r
17                 object.mock = function(mockedType) {\r
18                         if(!mockedType) {\r
19                                 throw new Mock4JSException("Cannot create mock: type to mock cannot be found or is null");\r
20                         }\r
21                         var newMock = new Mock(mockedType);\r
22                         Mock4JS._mocksToVerify.push(newMock);\r
23                         return newMock;\r
24                 }\r
25 \r
26                 // syntactic sugar for expects()\r
27                 object.once = function() {\r
28                         return new CallCounter(1);\r
29                 }\r
30                 object.never = function() {\r
31                         return new CallCounter(0);\r
32                 }\r
33                 object.exactly = function(expectedCallCount) {\r
34                         return new CallCounter(expectedCallCount);\r
35                 }\r
36                 object.atLeastOnce = function() {\r
37                         return new InvokeAtLeastOnce();\r
38                 }\r
39                 \r
40                 // syntactic sugar for argument expectations\r
41                 object.ANYTHING = new MatchAnything();\r
42                 object.NOT_NULL = new MatchAnythingBut(new MatchExactly(null));\r
43                 object.NOT_UNDEFINED = new MatchAnythingBut(new MatchExactly(undefined));\r
44                 object.eq = function(expectedValue) {\r
45                         return new MatchExactly(expectedValue);\r
46                 }\r
47                 object.not = function(valueNotExpected) {\r
48                         var argConstraint = Mock4JS._convertToConstraint(valueNotExpected);\r
49                         return new MatchAnythingBut(argConstraint);\r
50                 }\r
51                 object.and = function() {\r
52                         var constraints = [];\r
53                         for(var i=0; i<arguments.length; i++) {\r
54                                 constraints[i] = Mock4JS._convertToConstraint(arguments[i]);\r
55                         }\r
56                         return new MatchAllOf(constraints);\r
57                 }\r
58                 object.or = function() {\r
59                         var constraints = [];\r
60                         for(var i=0; i<arguments.length; i++) {\r
61                                 constraints[i] = Mock4JS._convertToConstraint(arguments[i]);\r
62                         }\r
63                         return new MatchAnyOf(constraints);\r
64                 }\r
65                 object.stringContains = function(substring) {\r
66                         return new MatchStringContaining(substring);\r
67                 }\r
68                 \r
69                 // syntactic sugar for will()\r
70                 object.returnValue = function(value) {\r
71                         return new ReturnValueAction(value);\r
72                 }\r
73                 object.throwException = function(exception) {\r
74                         return new ThrowExceptionAction(exception);\r
75                 }\r
76         },\r
77         clearMocksToVerify: function() {\r
78                 Mock4JS._mocksToVerify = [];\r
79         },\r
80         verifyAllMocks: function() {\r
81                 for(var i=0; i<Mock4JS._mocksToVerify.length; i++) {\r
82                         Mock4JS._mocksToVerify[i].verify();\r
83                 }\r
84         }\r
85 }\r
86 \r
87 Mock4JSUtil = {\r
88         hasFunction: function(obj, methodName) {\r
89                 return typeof obj == 'object' && typeof obj[methodName] == 'function';\r
90         },\r
91         join: function(list) {\r
92                 var result = "";\r
93                 for(var i=0; i<list.length; i++) {\r
94                         var item = list[i];\r
95                         if(Mock4JSUtil.hasFunction(item, "describe")) {\r
96                                 result += item.describe();\r
97                         }\r
98                         else if(typeof list[i] == 'string') {\r
99                                 result += "\""+list[i]+"\"";\r
100                         } else {\r
101                                 result += list[i];\r
102                         }\r
103                         \r
104                         if(i<list.length-1) result += ", ";\r
105                 }\r
106                 return result;\r
107         }       \r
108 }\r
109 \r
110 Mock4JSException = function(message) {\r
111         this.message = message;\r
112 }\r
113 \r
114 Mock4JSException.prototype = {\r
115         toString: function() {\r
116                 return this.message;\r
117         }\r
118 }\r
119 \r
120 /**\r
121  * Assert function that makes use of the constraint methods\r
122  */ \r
123 assertThat = function(expected, argumentMatcher) {\r
124         if(!argumentMatcher.argumentMatches(expected)) {\r
125                 fail("Expected '"+expected+"' to be "+argumentMatcher.describe());\r
126         }\r
127 }\r
128 \r
129 /**\r
130  * CallCounter\r
131  */\r
132 function CallCounter(expectedCount) {\r
133         this._expectedCallCount = expectedCount;\r
134         this._actualCallCount = 0;\r
135 }\r
136 \r
137 CallCounter.prototype = {\r
138         addActualCall: function() {\r
139                 this._actualCallCount++;\r
140                 if(this._actualCallCount > this._expectedCallCount) {\r
141                         throw new Mock4JSException("unexpected invocation");\r
142                 }\r
143         },\r
144         \r
145         verify: function() {\r
146                 if(this._actualCallCount < this._expectedCallCount) {\r
147                         throw new Mock4JSException("expected method was not invoked the expected number of times");\r
148                 }\r
149         },\r
150         \r
151         describe: function() {\r
152                 if(this._expectedCallCount == 0) {\r
153                         return "not expected";\r
154                 } else if(this._expectedCallCount == 1) {\r
155                         var msg = "expected once";\r
156                         if(this._actualCallCount >= 1) {\r
157                                 msg += " and has been invoked";\r
158                         }\r
159                         return msg;\r
160                 } else {\r
161                         var msg = "expected "+this._expectedCallCount+" times";\r
162                         if(this._actualCallCount > 0) {\r
163                                 msg += ", invoked "+this._actualCallCount + " times";\r
164                         }\r
165                         return msg;\r
166                 }\r
167         }\r
168 }\r
169 \r
170 function InvokeAtLeastOnce() {\r
171         this._hasBeenInvoked = false;\r
172 }\r
173 \r
174 InvokeAtLeastOnce.prototype = {\r
175         addActualCall: function() {\r
176                 this._hasBeenInvoked = true;\r
177         },\r
178         \r
179         verify: function() {\r
180                 if(this._hasBeenInvoked === false) {\r
181                         throw new Mock4JSException(describe());\r
182                 }\r
183         },\r
184         \r
185         describe: function() {\r
186                 var desc = "expected at least once";\r
187                 if(this._hasBeenInvoked) desc+=" and has been invoked";\r
188                 return desc;\r
189         }\r
190 }\r
191 \r
192 /**\r
193  * ArgumentMatchers\r
194  */\r
195 \r
196 function MatchExactly(expectedValue) {\r
197         this._expectedValue = expectedValue;\r
198 }\r
199 \r
200 MatchExactly.prototype = {\r
201         argumentMatches: function(actualArgument) {\r
202                 if(this._expectedValue instanceof Array) {\r
203                         if(!(actualArgument instanceof Array)) return false;\r
204                         if(this._expectedValue.length != actualArgument.length) return false;\r
205                         for(var i=0; i<this._expectedValue.length; i++) {\r
206                                 if(this._expectedValue[i] != actualArgument[i]) return false;\r
207                         }\r
208                         return true;\r
209                 } else {\r
210                         return this._expectedValue == actualArgument;\r
211                 }\r
212         },\r
213         describe: function() {\r
214                 if(typeof this._expectedValue == "string") {\r
215                         return "eq(\""+this._expectedValue+"\")";\r
216                 } else {\r
217                         return "eq("+this._expectedValue+")";\r
218                 }\r
219         }\r
220 }\r
221 \r
222 function MatchAnything() {\r
223 }\r
224 \r
225 MatchAnything.prototype = {\r
226         argumentMatches: function(actualArgument) {\r
227                 return true;\r
228         },\r
229         describe: function() {\r
230                 return "ANYTHING";\r
231         }\r
232 }\r
233 \r
234 function MatchAnythingBut(matcherToNotMatch) {\r
235         this._matcherToNotMatch = matcherToNotMatch;\r
236 }\r
237 \r
238 MatchAnythingBut.prototype = {\r
239         argumentMatches: function(actualArgument) {\r
240                 return !this._matcherToNotMatch.argumentMatches(actualArgument);\r
241         },\r
242         describe: function() {\r
243                 return "not("+this._matcherToNotMatch.describe()+")";\r
244         }\r
245 }\r
246 \r
247 function MatchAllOf(constraints) {\r
248         this._constraints = constraints;\r
249 }\r
250 \r
251 \r
252 MatchAllOf.prototype = {\r
253         argumentMatches: function(actualArgument) {\r
254                 for(var i=0; i<this._constraints.length; i++) {\r
255                         var constraint = this._constraints[i];\r
256                         if(!constraint.argumentMatches(actualArgument)) return false;\r
257                 }\r
258                 return true;\r
259         },\r
260         describe: function() {\r
261                 return "and("+Mock4JSUtil.join(this._constraints)+")";\r
262         }\r
263 }\r
264 \r
265 function MatchAnyOf(constraints) {\r
266         this._constraints = constraints;\r
267 }\r
268 \r
269 MatchAnyOf.prototype = {\r
270         argumentMatches: function(actualArgument) {\r
271                 for(var i=0; i<this._constraints.length; i++) {\r
272                         var constraint = this._constraints[i];\r
273                         if(constraint.argumentMatches(actualArgument)) return true;\r
274                 }\r
275                 return false;\r
276         },\r
277         describe: function() {\r
278                 return "or("+Mock4JSUtil.join(this._constraints)+")";\r
279         }\r
280 }\r
281 \r
282 \r
283 function MatchStringContaining(stringToLookFor) {\r
284         this._stringToLookFor = stringToLookFor;\r
285 }\r
286 \r
287 MatchStringContaining.prototype = {\r
288         argumentMatches: function(actualArgument) {\r
289                 if(typeof actualArgument != 'string') throw new Mock4JSException("stringContains() must be given a string, actually got a "+(typeof actualArgument));\r
290                 return (actualArgument.indexOf(this._stringToLookFor) != -1);\r
291         },\r
292         describe: function() {\r
293                 return "a string containing \""+this._stringToLookFor+"\"";\r
294         }\r
295 }\r
296 \r
297 \r
298 /**\r
299  * StubInvocation\r
300  */\r
301 function StubInvocation(expectedMethodName, expectedArgs, actionSequence) {\r
302         this._expectedMethodName = expectedMethodName;\r
303         this._expectedArgs = expectedArgs;\r
304         this._actionSequence = actionSequence;\r
305 }\r
306 \r
307 StubInvocation.prototype = {\r
308         matches: function(invokedMethodName, invokedMethodArgs) {\r
309                 if (invokedMethodName != this._expectedMethodName) {\r
310                         return false;\r
311                 }\r
312                 \r
313                 if (invokedMethodArgs.length != this._expectedArgs.length) {\r
314                         return false;\r
315                 }\r
316                 \r
317                 for(var i=0; i<invokedMethodArgs.length; i++) {\r
318                         var expectedArg = this._expectedArgs[i];\r
319                         var invokedArg = invokedMethodArgs[i];\r
320                         if(!expectedArg.argumentMatches(invokedArg)) {\r
321                                 return false;\r
322                         }\r
323                 }\r
324                 \r
325                 return true;\r
326         },\r
327         \r
328         invoked: function() {\r
329                 try {\r
330                         return this._actionSequence.invokeNextAction();\r
331                 } catch(e) {\r
332                         if(e instanceof Mock4JSException) {\r
333                                 throw new Mock4JSException(this.describeInvocationNameAndArgs()+" - "+e.message);\r
334                         } else {\r
335                                 throw e;\r
336                         }\r
337                 }\r
338         },\r
339         \r
340         will: function() {\r
341                 this._actionSequence.addAll.apply(this._actionSequence, arguments);\r
342         },\r
343         \r
344         describeInvocationNameAndArgs: function() {\r
345                 return this._expectedMethodName+"("+Mock4JSUtil.join(this._expectedArgs)+")";\r
346         },\r
347         \r
348         describe: function() {\r
349                 return "stub: "+this.describeInvocationNameAndArgs();\r
350         },\r
351         \r
352         verify: function() {\r
353         }\r
354 }\r
355 \r
356 /**\r
357  * ExpectedInvocation\r
358  */\r
359 function ExpectedInvocation(expectedMethodName, expectedArgs, expectedCallCounter) {\r
360         this._stubInvocation = new StubInvocation(expectedMethodName, expectedArgs, new ActionSequence());\r
361         this._expectedCallCounter = expectedCallCounter;\r
362 }\r
363 \r
364 ExpectedInvocation.prototype = {\r
365         matches: function(invokedMethodName, invokedMethodArgs) {\r
366                 try {\r
367                         return this._stubInvocation.matches(invokedMethodName, invokedMethodArgs);\r
368                 } catch(e) {\r
369                         throw new Mock4JSException("method "+this._stubInvocation.describeInvocationNameAndArgs()+": "+e.message);\r
370                 }\r
371         },\r
372         \r
373         invoked: function() {\r
374                 try {\r
375                         this._expectedCallCounter.addActualCall();\r
376                 } catch(e) {\r
377                         throw new Mock4JSException(e.message+": "+this._stubInvocation.describeInvocationNameAndArgs());\r
378                 }\r
379                 return this._stubInvocation.invoked();\r
380         },\r
381         \r
382         will: function() {\r
383                 this._stubInvocation.will.apply(this._stubInvocation, arguments);\r
384         },\r
385         \r
386         describe: function() {\r
387                 return this._expectedCallCounter.describe()+": "+this._stubInvocation.describeInvocationNameAndArgs();\r
388         },\r
389         \r
390         verify: function() {\r
391                 try {\r
392                         this._expectedCallCounter.verify();\r
393                 } catch(e) {\r
394                         throw new Mock4JSException(e.message+": "+this._stubInvocation.describeInvocationNameAndArgs());\r
395                 }\r
396         }\r
397 }\r
398 \r
399 /**\r
400  * MethodActions\r
401  */\r
402 function ReturnValueAction(valueToReturn) {\r
403         this._valueToReturn = valueToReturn;\r
404 }\r
405 \r
406 ReturnValueAction.prototype = {\r
407         invoke: function() {\r
408                 return this._valueToReturn;\r
409         },\r
410         describe: function() {\r
411                 return "returns "+this._valueToReturn;\r
412         }\r
413 }\r
414 \r
415 function ThrowExceptionAction(exceptionToThrow) {\r
416         this._exceptionToThrow = exceptionToThrow;\r
417 }\r
418 \r
419 ThrowExceptionAction.prototype = {\r
420         invoke: function() {\r
421                 throw this._exceptionToThrow;\r
422         },\r
423         describe: function() {\r
424                 return "throws "+this._exceptionToThrow;\r
425         }\r
426 }\r
427 \r
428 function ActionSequence() {\r
429         this._ACTIONS_NOT_SETUP = "_ACTIONS_NOT_SETUP";\r
430         this._actionSequence = this._ACTIONS_NOT_SETUP;\r
431         this._indexOfNextAction = 0;\r
432 }\r
433 \r
434 ActionSequence.prototype = {\r
435         invokeNextAction: function() {\r
436                 if(this._actionSequence === this._ACTIONS_NOT_SETUP) {\r
437                         return;\r
438                 } else {\r
439                         if(this._indexOfNextAction >= this._actionSequence.length) {\r
440                                 throw new Mock4JSException("no more values to return");\r
441                         } else {\r
442                                 var action = this._actionSequence[this._indexOfNextAction];\r
443                                 this._indexOfNextAction++;\r
444                                 return action.invoke();\r
445                         }\r
446                 }\r
447         },\r
448         \r
449         addAll: function() {\r
450                 this._actionSequence = [];\r
451                 for(var i=0; i<arguments.length; i++) {\r
452                         if(typeof arguments[i] != 'object' && arguments[i].invoke === undefined) {\r
453                                 throw new Error("cannot add a method action that does not have an invoke() method");\r
454                         }\r
455                         this._actionSequence.push(arguments[i]);\r
456                 }\r
457         }\r
458 }\r
459 \r
460 function StubActionSequence() {\r
461         this._ACTIONS_NOT_SETUP = "_ACTIONS_NOT_SETUP";\r
462         this._actionSequence = this._ACTIONS_NOT_SETUP;\r
463         this._indexOfNextAction = 0;\r
464\r
465 \r
466 StubActionSequence.prototype = {\r
467         invokeNextAction: function() {\r
468                 if(this._actionSequence === this._ACTIONS_NOT_SETUP) {\r
469                         return;\r
470                 } else if(this._actionSequence.length == 1) {\r
471                         // if there is only one method action, keep doing that on every invocation\r
472                         return this._actionSequence[0].invoke();\r
473                 } else {\r
474                         if(this._indexOfNextAction >= this._actionSequence.length) {\r
475                                 throw new Mock4JSException("no more values to return");\r
476                         } else {\r
477                                 var action = this._actionSequence[this._indexOfNextAction];\r
478                                 this._indexOfNextAction++;\r
479                                 return action.invoke();\r
480                         }\r
481                 }\r
482         },\r
483         \r
484         addAll: function() {\r
485                 this._actionSequence = [];\r
486                 for(var i=0; i<arguments.length; i++) {\r
487                         if(typeof arguments[i] != 'object' && arguments[i].invoke === undefined) {\r
488                                 throw new Error("cannot add a method action that does not have an invoke() method");\r
489                         }\r
490                         this._actionSequence.push(arguments[i]);\r
491                 }\r
492         }\r
493 }\r
494 \r
495  \r
496 /**\r
497  * Mock\r
498  */\r
499 function Mock(mockedType) {\r
500         if(mockedType === undefined || mockedType.prototype === undefined) {\r
501                 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)'");\r
502         }\r
503         this._mockedType = mockedType.prototype;\r
504         this._expectedCallCount;\r
505         this._isRecordingExpectations = false;\r
506         this._expectedInvocations = [];\r
507 \r
508         // setup proxy\r
509         var IntermediateClass = new Function();\r
510         IntermediateClass.prototype = mockedType.prototype;\r
511         var ChildClass = new Function();\r
512         ChildClass.prototype = new IntermediateClass();\r
513         this._proxy = new ChildClass();\r
514         this._proxy.mock = this;\r
515         \r
516         for(property in mockedType.prototype) {\r
517                 if(this._isPublicMethod(mockedType.prototype, property)) {\r
518                         var publicMethodName = property;\r
519                         this._proxy[publicMethodName] = this._createMockedMethod(publicMethodName);\r
520                         this[publicMethodName] = this._createExpectationRecordingMethod(publicMethodName);\r
521                 }\r
522         }\r
523 }\r
524 \r
525 Mock.prototype = {\r
526         \r
527         proxy: function() {\r
528                 return this._proxy;\r
529         },\r
530         \r
531         expects: function(expectedCallCount) {\r
532                 this._expectedCallCount = expectedCallCount;\r
533                 this._isRecordingExpectations = true;\r
534                 this._isRecordingStubs = false;\r
535                 return this;\r
536         },\r
537         \r
538         stubs: function() {\r
539                 this._isRecordingExpectations = false;\r
540                 this._isRecordingStubs = true;\r
541                 return this;\r
542         },\r
543         \r
544         verify: function() {\r
545                 for(var i=0; i<this._expectedInvocations.length; i++) {\r
546                         var expectedInvocation = this._expectedInvocations[i];\r
547                         try {\r
548                                 expectedInvocation.verify();\r
549                         } catch(e) {\r
550                                 var failMsg = e.message+this._describeMockSetup();\r
551                                 throw new Mock4JSException(failMsg);\r
552                         }\r
553                 }\r
554         },\r
555         \r
556         _isPublicMethod: function(mockedType, property) {\r
557                 try {\r
558                         var isMethod = typeof(mockedType[property]) == 'function';\r
559                         var isPublic = property.charAt(0) != "_"; \r
560                         return isMethod && isPublic;\r
561                 } catch(e) {\r
562                         return false;\r
563                 }\r
564         },\r
565 \r
566         _createExpectationRecordingMethod: function(methodName) {\r
567                 return function() {\r
568                         // ensure all arguments are instances of ArgumentMatcher\r
569                         var expectedArgs = [];\r
570                         for(var i=0; i<arguments.length; i++) {\r
571                                 if(arguments[i] !== null && arguments[i] !== undefined && arguments[i].argumentMatches) {\r
572                                         expectedArgs[i] = arguments[i];\r
573                                 } else {\r
574                                         expectedArgs[i] = new MatchExactly(arguments[i]);\r
575                                 }\r
576                         }\r
577                         \r
578                         // create stub or expected invocation\r
579                         var expectedInvocation;\r
580                         if(this._isRecordingExpectations) {\r
581                                 expectedInvocation = new ExpectedInvocation(methodName, expectedArgs, this._expectedCallCount);\r
582                         } else {\r
583                                 expectedInvocation = new StubInvocation(methodName, expectedArgs, new StubActionSequence());\r
584                         }\r
585                         \r
586                         this._expectedInvocations.push(expectedInvocation);\r
587                         \r
588                         this._isRecordingExpectations = false;\r
589                         this._isRecordingStubs = false;\r
590                         return expectedInvocation;\r
591                 }\r
592         },\r
593         \r
594         _createMockedMethod: function(methodName) {\r
595                 return function() {\r
596                         // go through expectation list backwards to ensure later expectations override earlier ones\r
597                         for(var i=this.mock._expectedInvocations.length-1; i>=0; i--) {\r
598                                 var expectedInvocation = this.mock._expectedInvocations[i];\r
599                                 if(expectedInvocation.matches(methodName, arguments)) {\r
600                                         try {\r
601                                                 return expectedInvocation.invoked();\r
602                                         } catch(e) {\r
603                                                 if(e instanceof Mock4JSException) {\r
604                                                         throw new Mock4JSException(e.message+this.mock._describeMockSetup());\r
605                                                 } else {\r
606                                                         // the user setup the mock to throw a specific error, so don't modify the message\r
607                                                         throw e;\r
608                                                 }\r
609                                         }\r
610                                 }\r
611                         }\r
612                         var failMsg = "unexpected invocation: "+methodName+"("+Mock4JSUtil.join(arguments)+")"+this.mock._describeMockSetup();\r
613                         throw new Mock4JSException(failMsg);\r
614                 };\r
615         },\r
616         \r
617         _describeMockSetup: function() {\r
618                 var msg = "\nAllowed:";\r
619                 for(var i=0; i<this._expectedInvocations.length; i++) {\r
620                         var expectedInvocation = this._expectedInvocations[i];\r
621                         msg += "\n" + expectedInvocation.describe();\r
622                 }\r
623                 return msg;\r
624         }\r
625 }