2 * Jake JavaScript build tool
3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 let path = require('path');
20 let currDir = process.cwd();
29 @description Instantiating a TestTask creates a number of Jake
30 Tasks that make running tests for your software easy.
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.
39 let t = new jake.TestTask('bij-js', function () {
40 this.testName = 'testSpecial';
41 this.testFiles.include('test/**');
45 let TestTask = function () {
47 let args = Array.prototype.slice.call(arguments);
48 let name = args.shift();
49 let definition = args.pop();
50 let prereqs = args.pop() || [];
53 @name jake.TestTask#testNam
56 @description The name of the namespace to place the tests in, and
57 the top-level task for running tests. Defaults to "test"
59 this.testName = 'test';
62 @name jake.TestTask#testFiles
65 @description The list of files containing tests to load
67 this.testFiles = new jake.FileList();
70 @name jake.TestTask#showDescription
73 @description Show the created task when doing Jake -T
75 this.showDescription = true;
78 @name jake.TestTask#totalTests
81 @description The total number of tests to run
86 @name jake.TestTask#executedTests
89 @description The number of tests successfully run
91 this.executedTests = 0;
93 if (typeof definition == 'function') {
94 definition.call(this);
97 if (this.showDescription) {
98 desc('Run the tests for ' + name);
101 task(this.testName, prereqs, {async: true}, function () {
102 let t = jake.Task[this.fullName + ':run'];
103 t.on('complete', function () {
106 // Pass args to the namespaced test
107 t.invoke.apply(t, arguments);
110 namespace(self.testName, function () {
112 let runTask = task('run', {async: true}, function (pat) {
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;
120 re = new RegExp(pat);
121 // Get test files that match the passed-in pattern
122 testFiles = self.testFiles.toArray()
123 .filter(function (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) {
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
137 // Continuation to pass to the async tests, wrapping `continune`
138 let next = function () {
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
146 let isAsync = !!action.length;
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), {
153 t.once('complete', function () {
154 self.executedTests++;
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
168 if (!(n == 'before' || n == 'after' ||
169 /_beforeEach$/.test(n) || /_afterEach$/.test(n))) {
172 // 'this' will be the task when action is run
173 return a.call(this, cb);
176 // Dummy top-level task for everything to be prereqs for
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));
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 + ':';
189 // Dummy task for displaying file banner
190 testName = '*** Running ' + file + ' ***';
191 prereqs.push(testPrefix + testName);
192 createTask(testName, function () {});
195 if (typeof exp.before == 'function') {
196 prereqs.push(testPrefix + 'before');
198 createTask('before', exp.before);
201 // Walk each exported function, and create a task for each
203 if (p == 'before' || p == 'after' ||
204 p == 'beforeEach' || p == 'afterEach') {
208 if (typeof exp.beforeEach == 'function') {
209 prereqs.push(testPrefix + p + '_beforeEach');
211 createTask(p + '_beforeEach', exp.beforeEach);
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);
218 createTask(p, exp[p]);
220 if (typeof exp.afterEach == 'function') {
221 prereqs.push(testPrefix + p + '_afterEach');
223 createTask(p + '_afterEach', exp.afterEach);
228 if (typeof exp.after == 'function') {
229 prereqs.push(testPrefix + 'after');
231 let afterTask = createTask('after', exp.after);
232 afterTask._internal = true;
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.');
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');
257 topLevel.invoke(); // Do the thing!
261 runTask._internal = true;
268 jake.TestTask = TestTask;
269 exports.TestTask = TestTask;