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