1 // ========================================================================== 2 // Project: SproutCore Costello - Property Observing 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 8 /*globals CoreTest module */ 9 10 /** @class 11 12 A test Suite defines a group of reusable unit tests that can be added to a 13 test plan at any time by calling the generate() method. Suites are most 14 useful for defining groups of tests that validate compliance with a mixin. 15 You can then generate customized versions of the test suite for different 16 types of objects to ensure that both the mixin and the object implementing 17 the mixin use the API properly. 18 19 ## Using a Suite 20 21 To use a Suite, call the generate() method on the suite inside on of your 22 unit test files. This will generate new modules and tests in the suite 23 and add them to your test plan. 24 25 Usually you will need to customize the suite to apply to a specific object. 26 You can supply these customizations through an attribute hash passed to the 27 generate() method. See the documentation on the specific test suite for 28 information on the kind of customizations you may need to provide. 29 30 ### Example 31 32 // generates the SC.ArrayTestSuite tests for a built-in array. 33 SC.ArrayTests.generate('Array', { 34 newObject: function() { return []; } 35 }); 36 37 ## Defining a Suite 38 39 To define a test suite, simply call the extend() method, passing any 40 attributes you want to define on the suite along with this method. You can 41 then add functions that will define the test suite with the define() method. 42 43 Functions you pass to define will have an instance of the test suite passed 44 as their first parameter when invoked. 45 46 ### Example 47 48 SC.ArrayTests = CoreTest.Suite.create("Verify SC.Array compliance", { 49 50 // override to generate a new object that implements SC.Array 51 newObject: function() { return null; } 52 }); 53 54 SC.ArrayTests.define(function(T) { 55 T.module("length tests"); 56 57 test("new length", function() { 58 equals(T.object.get('length'), 0, 'array length'); 59 }); 60 61 }); 62 63 @since SproutCore 1.0 64 65 */ 66 CoreTest.Suite = /** @scope CoreTest.Suite.prototype */ { 67 68 /** 69 Call this method to define a new test suite. Pass one or more hashes of 70 properties you want added to the new suite. 71 72 @param {Hash} attrs one or more attribute hashes 73 @returns {CoreTest.Suite} subclass of suite. 74 */ 75 create: function(desc, attrs) { 76 var len = arguments.length, 77 ret = CoreTest.beget(this), 78 idx; 79 80 // copy any attributes 81 for(idx=1;idx<len;idx++) CoreTest.mixin(ret, arguments[idx]); 82 83 if (desc) ret.basedesc = desc; 84 85 // clone so that new definitions will be kept separate 86 ret.definitions = ret.definitions.slice(); 87 88 return ret ; 89 }, 90 91 /** 92 Generate a new test suite instance, adding the suite definitions to the 93 current test plan. Pass a description of the test suite as well as one or 94 more attribute hashes to apply to the test plan. 95 96 The description you add will be prefixed in front of the 'desc' property 97 on the test plan itself. 98 99 @param {String} desc suite description 100 @param {Hash} attrs one or more attribute hashes 101 @returns {CoreTest.Suite} suite instance 102 */ 103 generate: function(desc, attrs) { 104 var len = arguments.length, 105 ret = CoreTest.beget(this), 106 idx, defs; 107 108 // apply attributes - skip first argument b/c it is a string 109 for(idx=1;idx<len;idx++) CoreTest.mixin(ret, arguments[idx]); 110 ret.subdesc = desc ; 111 112 // invoke definitions 113 defs = ret.definitions ; 114 len = defs.length; 115 for(idx=0;idx<len;idx++) defs[idx].call(ret, ret); 116 117 return ret ; 118 }, 119 120 /** 121 Adds the passed function to the array of definitions that will be invoked 122 when the suite is generated. 123 124 The passed function should expect to have the TestSuite instance passed 125 as the first and only parameter. The function should actually define 126 a module and tests, which will be added to the test suite. 127 128 @param {Function} func definition function 129 @returns {CoreTest.Suite} receiver 130 */ 131 define: function(func) { 132 this.definitions.push(func); 133 return this ; 134 }, 135 136 /** 137 Definition functions. These are invoked in order when you generate a 138 suite to add unit tests and modules to the test plan. 139 */ 140 definitions: [], 141 142 /** 143 Generates a module description by merging the based description, sub 144 description and the passed description. This is usually used inside of 145 a suite definition function. 146 147 @param {String} str detailed description for this module 148 @returns {String} generated description 149 */ 150 desc: function(str) { 151 return this.basedesc.fmt(this.subdesc, str); 152 }, 153 154 /** 155 The base description string. This should accept two formatting options, 156 a sub description and a detailed description. This is the description 157 set when you call extend() 158 */ 159 basedesc: "%@ > %@", 160 161 /** 162 Default setup method for use with modules. This method will call the 163 newObject() method and set its return value on the object property of 164 the receiver. 165 */ 166 setup: function() { 167 this.object = this.newObject(); 168 }, 169 170 /** 171 Default teardown method for use with modules. This method will call the 172 destroyObject() method, passing the current object property on the 173 receiver. It will also clear the object property. 174 */ 175 teardown: function() { 176 if (this.object) this.destroyObject(this.object); 177 this.object = null; 178 }, 179 180 /** 181 Default method to create a new object instance. You will probably want 182 to override this method when you generate() a suite with a function that 183 can generate the type of object you want to test. 184 185 @returns {Object} generated object 186 */ 187 newObject: function() { return null; }, 188 189 /** 190 Default method to destroy a generated object instance after a test has 191 completed. If you override newObject() you can also override this method 192 to cleanup the object you just created. 193 194 Default method does nothing. 195 */ 196 destroyObject: function(obj) { 197 // do nothing. 198 }, 199 200 /** 201 Generates a default module with the description you provide. This is 202 a convenience function for use inside of a definition function. You could 203 do the same thing by calling: 204 205 var T = this ; 206 module(T.desc(description), { 207 setup: function() { T.setup(); }, 208 teardown: function() { T.teardown(); } 209 } 210 211 @param {String} desc detailed description 212 @returns {CoreTest.Suite} receiver 213 */ 214 module: function(desc) { 215 var T = this ; 216 module(T.desc(desc), { 217 setup: function() { T.setup(); }, 218 teardown: function() { T.teardown(); } 219 }); 220 } 221 222 }; 223