1 // ==========================================================================
  2 // Project:   SproutCore Unit Testing Library
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 /*globals CoreTest */
  8 
  9 // these compiler directives are normally defined in runtime's core.  But
 10 // since the testing framework needs to be totally independent, we redefine
 11 // them here also.
 12 var require = require || function sc_require() {};
 13 var sc_require = sc_require || require;
 14 var sc_resource = sc_resource || function sc_resource() {};
 15 
 16 // map used to exist, this is here for backwards compatibility
 17 var Q$ = jQuery;
 18 
 19 /** @namespace
 20 
 21   CoreTest is the unit testing library for SproutCore.  It includes a test 
 22   runner based on QUnit with some useful extensions for testing SproutCore-
 23   based applications.
 24   
 25   You can use CoreTest just like you would use QUnit in your tests directory.
 26 */
 27 CoreTest = {
 28   
 29   /** 
 30     Empty function.  Useful for some operations. 
 31   */
 32   K: function() { return this; },
 33 
 34   /**
 35     Copied from SproutCore Runtime Core.  Included here to avoid dependencies.
 36 
 37     @param obj {Object} the object to beget
 38     @returns {Object} the new object.
 39   */
 40   beget: function(obj) {
 41     if (!obj) return null ;
 42     var K = CoreTest.K; K.prototype = obj ;
 43     var ret = new K();
 44     K.prototype = null ; // avoid leaks
 45     return ret ;
 46   },
 47   
 48   /**
 49     Copied from SproutCore Runtime Core.  Included here to avoid dependencies.
 50 
 51     @param target {Object} the target object to extend
 52     @param properties {Object} one or more objects with properties to copy.
 53     @returns {Object} the target object.
 54     @static
 55   */
 56   mixin: function() {
 57     // copy reference to target object
 58     var target = arguments[0] || {};
 59     var idx = 1;
 60     var length = arguments.length ;
 61     var options ;
 62 
 63     // Handle case where we have only one item...extend CoreTest
 64     if (length === 1) {
 65       target = this || {};
 66       idx=0;
 67     }
 68 
 69     for ( ; idx < length; idx++ ) {
 70       if (!(options = arguments[idx])) continue ;
 71       for(var key in options) {
 72         if (!options.hasOwnProperty(key)) continue ;
 73         var src = target[key];
 74         var copy = options[key] ;
 75         if (target===copy) continue ; // prevent never-ending loop
 76         if (copy !== undefined) target[key] = copy ;
 77       }
 78     }
 79 
 80     return target;
 81   },
 82   
 83   
 84   /** Borrowed from SproutCore Runtime Core */
 85   fmt: function(str) {
 86     // first, replace any ORDERED replacements.
 87     var args = arguments;
 88     var idx  = 1; // the current index for non-numerical replacements
 89     return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
 90       argIndex = (argIndex) ? parseInt(argIndex,0) : idx++ ;
 91       s =args[argIndex];
 92       return ((s===null) ? '(null)' : (s===undefined) ? '' : s).toString(); 
 93     }) ;
 94   },
 95   
 96   /**
 97     Returns a stub function that records any passed arguments and a call
 98     count.  You can pass no parameters, a single function or a hash.  
 99     
100     If you pass no parameters, then this simply returns a function that does 
101     nothing but record being called.  
102     
103     If you pass a function, then the function will execute when the method is
104     called, allowing you to stub in some fake behavior.
105     
106     If you pass a hash, you can supply any properties you want attached to the
107     stub function.  The two most useful are "action", which is the function 
108     that will execute when the stub runs (as if you just passed a function), 
109     and "expect" which should evaluate the stub results.
110     
111     In your unit test you can verify the stub by calling stub.expect(X), 
112     where X is the number of times you expect the function to be called.  If
113     you implement your own test function, you can actually pass whatever you
114     want.
115     
116     Calling stub.reset() will reset the record on the stub for further 
117     testing.
118 
119     @param {String} name the name of the stub to use for logging
120     @param {Function|Hash} func the function or hash
121     @returns {Function} stub function
122   */
123   stub: function(name, func) {  
124 
125     // normalize param
126     var attrs = {};
127     if (typeof func === "function") {
128       attrs.action = func;
129     } else if (typeof func === "object") {
130       attrs = func ;
131     }
132 
133     // create basic stub
134     var ret = function() {
135       ret.callCount++;
136       
137       // get arguments into independent array and save in history
138       var args = [], loc = arguments.length;
139       while(--loc >= 0) args[loc] = arguments[loc];
140       args.unshift(this); // save context
141       ret.history.push(args);
142       
143       return ret.action.apply(this, arguments);
144     };
145     ret.callCount = 0 ;
146     ret.history = [];
147     ret.stubName = name ;
148 
149     // copy attrs
150     var key;
151     for(key in attrs) {
152       if (!attrs.hasOwnProperty(key)) continue ;
153       ret[key] = attrs[key];
154     }
155 
156     // add on defaults
157     if (!ret.reset) {
158       ret.reset = function() {
159         this.callCount = 0;
160         this.history = [];
161       };
162     }
163     
164     if (!ret.action) {
165       ret.action = function() { return this; };
166     }
167     
168     if (!ret.expect) {
169       ret.expect = function(callCount, msg) {
170         if (callCount === YES) {
171           if (!msg) msg = CoreTest.fmt("%@ should be called at least once", this.stubName);
172           ok(this.callCount > 0, msg);
173         } else {
174           if (callCount === NO) callCount = 0;
175           if (!msg) msg = CoreTest.fmt("%@ should be called X times", this.stubName);
176           equals(this.callCount, callCount, msg);
177         }
178       };
179     }
180     
181     return ret ;
182   },
183 
184   /** Test is OK */
185   OK: 'passed',
186 
187   /** Test failed */
188   FAIL: 'failed',
189 
190   /** Test raised exception */
191   ERROR: 'errors',
192 
193   /** Test raised warning */
194   WARN: 'warnings',
195 
196   showUI : false,
197 
198   spyOn: function(object, method) {
199     if(!object) throw new Error('ERROR: Attempted to spy upon an invalid object');
200     if(!object[method]) throw new Error('ERROR: The requested method does not exist on the given object');
201 
202     var spy = new CoreTest.Spy;
203     object[method] = function() { spy.call(CoreTest.argumentsArray(arguments)) };
204     return spy;
205   },
206 
207   stubMethod: function(object, method) {
208     if(!object) throw new Error('ERROR: Attempted to spy upon an invalid object');
209     if(!object[method]) throw new Error('ERROR: The requested method does not exist on the given object');
210 
211     var stub = new CoreTest.Stub;
212     object[method] = function() { return stub.call() };
213     return stub;
214   }
215 };
216 
217 CoreTest.Spy = function() {
218   this.wasCalled = false;
219 };
220 
221 CoreTest.Spy.prototype.call = function(args) {
222   this.wasCalledWithArguments = args;
223   this.wasCalled = true;
224 };
225 
226 CoreTest.Spy.prototype.wasCalledWith = function() {
227   return CoreTest._isIdenticalArray(this.wasCalledWithArguments,CoreTest.argumentsArray(arguments));
228 };
229 
230 CoreTest.Stub = function() {
231 };
232 
233 CoreTest.Stub.prototype.andReturn = function(value) {
234   this.stubbedValue = value;
235 };
236 
237 CoreTest.Stub.prototype.call = function() {
238   if(this.stubbedValue === undefined) throw new Error('ERROR: You never specified what value the stub should return');
239   return this.stubbedValue;
240 };
241 
242 CoreTest.argumentsArray = function(args) {
243   var arrayOfArgs = [];
244   for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
245   return arrayOfArgs;
246 };
247 
248 CoreTest._isIdenticalArray = function(array1, array2) {
249   if(array1.length !== array2.length) return false;
250   for(var i = 0; i < array1.length; i++)
251     if(array1[i] !== array2[i]) return false;
252   return true;
253 };
254