1/* 2 * Jake JavaScript build tool 3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org) 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17*/ 18 19let path = require('path'); 20let currDir = process.cwd(); 21 22/** 23 @name jake 24 @namespace jake 25*/ 26/** 27 @name jake.TestTask 28 @constructor 29 @description Instantiating a TestTask creates a number of Jake 30 Tasks that make running tests for your software easy. 31 32 @param {String} name The name of the project 33 @param {Function} definition Defines the list of files containing the tests, 34 and the name of the namespace/task for running them. Will be executed on the 35 instantiated TestTask (i.e., 'this', will be the TestTask instance), to set 36 the various instance-propertiess. 37 38 @example 39 let t = new jake.TestTask('bij-js', function () { 40 this.testName = 'testSpecial'; 41 this.testFiles.include('test/**'); 42 }); 43 44 */ 45let TestTask = function () { 46 let self = this; 47 let args = Array.prototype.slice.call(arguments); 48 let name = args.shift(); 49 let definition = args.pop(); 50 let prereqs = args.pop() || []; 51 52 /** 53 @name jake.TestTask#testNam 54 @public 55 @type {String} 56 @description The name of the namespace to place the tests in, and 57 the top-level task for running tests. Defaults to "test" 58 */ 59 this.testName = 'test'; 60 61 /** 62 @name jake.TestTask#testFiles 63 @public 64 @type {jake.FileList} 65 @description The list of files containing tests to load 66 */ 67 this.testFiles = new jake.FileList(); 68 69 /** 70 @name jake.TestTask#showDescription 71 @public 72 @type {Boolean} 73 @description Show the created task when doing Jake -T 74 */ 75 this.showDescription = true; 76 77 /* 78 @name jake.TestTask#totalTests 79 @public 80 @type {Number} 81 @description The total number of tests to run 82 */ 83 this.totalTests = 0; 84 85 /* 86 @name jake.TestTask#executedTests 87 @public 88 @type {Number} 89 @description The number of tests successfully run 90 */ 91 this.executedTests = 0; 92 93 if (typeof definition == 'function') { 94 definition.call(this); 95 } 96 97 if (this.showDescription) { 98 desc('Run the tests for ' + name); 99 } 100 101 task(this.testName, prereqs, {async: true}, function () { 102 let t = jake.Task[this.fullName + ':run']; 103 t.on('complete', function () { 104 complete(); 105 }); 106 // Pass args to the namespaced test 107 t.invoke.apply(t, arguments); 108 }); 109 110 namespace(self.testName, function () { 111 112 let runTask = task('run', {async: true}, function (pat) { 113 let re; 114 let testFiles; 115 116 // Don't nest; make a top-level namespace. Don't want 117 // re-calling from inside to nest infinitely 118 jake.currentNamespace = jake.defaultNamespace; 119 120 re = new RegExp(pat); 121 // Get test files that match the passed-in pattern 122 testFiles = self.testFiles.toArray() 123 .filter(function (f) { 124 return (re).test(f); 125 }) // Don't load the same file multiple times -- should this be in FileList? 126 .reduce(function (p, c) { 127 if (p.indexOf(c) < 0) { 128 p.push(c); 129 } 130 return p; 131 }, []); 132 133 // Create a namespace for all the testing tasks to live in 134 namespace(self.testName + 'Exec', function () { 135 // Each test will be a prereq for the dummy top-level task 136 let prereqs = []; 137 // Continuation to pass to the async tests, wrapping `continune` 138 let next = function () { 139 complete(); 140 }; 141 // Create the task for this test-function 142 let createTask = function (name, action) { 143 // If the test-function is defined with a continuation 144 // param, flag the task as async 145 let t; 146 let isAsync = !!action.length; 147 148 // Define the actual namespaced task with the name, the 149 // wrapped action, and the correc async-flag 150 t = task(name, createAction(name, action), { 151 async: isAsync 152 }); 153 t.once('complete', function () { 154 self.executedTests++; 155 }); 156 t._internal = true; 157 return t; 158 }; 159 // Used as the action for the defined task for each test. 160 let createAction = function (n, a) { 161 // A wrapped function that passes in the `next` function 162 // for any tasks that run asynchronously 163 return function () { 164 let cb; 165 if (a.length) { 166 cb = next; 167 } 168 if (!(n == 'before' || n == 'after' || 169 /_beforeEach$/.test(n) || /_afterEach$/.test(n))) { 170 jake.logger.log(n); 171 } 172 // 'this' will be the task when action is run 173 return a.call(this, cb); 174 }; 175 }; 176 // Dummy top-level task for everything to be prereqs for 177 let topLevel; 178 179 // Pull in each test-file, and iterate over any exported 180 // test-functions. Register each test-function as a prereq task 181 testFiles.forEach(function (file) { 182 let exp = require(path.join(currDir, file)); 183 184 // Create a namespace for each filename, so test-name collisions 185 // won't be a problem 186 namespace(file, function () { 187 let testPrefix = self.testName + 'Exec:' + file + ':'; 188 let testName; 189 // Dummy task for displaying file banner 190 testName = '*** Running ' + file + ' ***'; 191 prereqs.push(testPrefix + testName); 192 createTask(testName, function () {}); 193 194 // 'before' setup 195 if (typeof exp.before == 'function') { 196 prereqs.push(testPrefix + 'before'); 197 // Create the task 198 createTask('before', exp.before); 199 } 200 201 // Walk each exported function, and create a task for each 202 for (let p in exp) { 203 if (p == 'before' || p == 'after' || 204 p == 'beforeEach' || p == 'afterEach') { 205 continue; 206 } 207 208 if (typeof exp.beforeEach == 'function') { 209 prereqs.push(testPrefix + p + '_beforeEach'); 210 // Create the task 211 createTask(p + '_beforeEach', exp.beforeEach); 212 } 213 214 // Add the namespace:name of this test to the list of prereqs 215 // for the dummy top-level task 216 prereqs.push(testPrefix + p); 217 // Create the task 218 createTask(p, exp[p]); 219 220 if (typeof exp.afterEach == 'function') { 221 prereqs.push(testPrefix + p + '_afterEach'); 222 // Create the task 223 createTask(p + '_afterEach', exp.afterEach); 224 } 225 } 226 227 // 'after' teardown 228 if (typeof exp.after == 'function') { 229 prereqs.push(testPrefix + 'after'); 230 // Create the task 231 let afterTask = createTask('after', exp.after); 232 afterTask._internal = true; 233 } 234 235 }); 236 }); 237 238 self.totalTests = prereqs.length; 239 process.on('exit', function () { 240 // Throw in the case where the process exits without 241 // finishing tests, but no error was thrown 242 if (!jake.errorCode && (self.totalTests > self.executedTests)) { 243 throw new Error('Process exited without all tests completing.'); 244 } 245 }); 246 247 // Create the dummy top-level task. When calling a task internally 248 // with `invoke` that is async (or has async prereqs), have to listen 249 // for the 'complete' event to know when it's done 250 topLevel = task('__top__', prereqs); 251 topLevel._internal = true; 252 topLevel.addListener('complete', function () { 253 jake.logger.log('All tests ran successfully'); 254 complete(); 255 }); 256 257 topLevel.invoke(); // Do the thing! 258 }); 259 260 }); 261 runTask._internal = true; 262 263 }); 264 265 266}; 267 268jake.TestTask = TestTask; 269exports.TestTask = TestTask; 270 271