UNPKG

5.82 kBJavaScriptView Raw
1/*
2 * grunt
3 * http://gruntjs.com/
4 *
5 * Copyright (c) 2012 "Cowboy" Ben Alman
6 * Licensed under the MIT license.
7 * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
8 */
9
10module.exports = function(grunt) {
11
12 // Nodejs libs.
13 var path = require('path');
14
15 // External libs.
16 var nodeunit = require('nodeunit');
17 var nodeunitUtils = require('nodeunit/lib/utils');
18
19 // ==========================================================================
20 // CUSTOM NODEUNIT REPORTER
21 // ==========================================================================
22
23 // Keep track of the last-started module.
24 var currentModule;
25 // Keep track of the last-started test(s).
26 var unfinished = {};
27
28 // If Nodeunit explodes because a test was missing test.done(), handle it.
29 process.on('exit', function() {
30 var len = Object.keys(unfinished).length;
31 // If there are unfinished tests, tell the user why Nodeunit killed grunt.
32 if (len > 0) {
33 grunt.log.muted = false;
34 grunt.verbose.error().or.writeln('F'.red);
35 grunt.log.error('Incomplete tests/setups/teardowns:');
36 Object.keys(unfinished).forEach(grunt.log.error, grunt.log);
37 grunt.fatal('A test was missing test.done(), so nodeunit exploded. Sorry!',
38 Math.min(99, 90 + len));
39 }
40 });
41
42 // Keep track of failed assertions for pretty-printing.
43 var failedAssertions = [];
44 function logFailedAssertions() {
45 var assertion, stack;
46 // Print each assertion error + stack.
47 while (assertion = failedAssertions.shift()) {
48 nodeunitUtils.betterErrors(assertion);
49 grunt.verbose.or.error(assertion.testName);
50 if (assertion.error.name === 'AssertionError' && assertion.message) {
51 grunt.log.error('AssertionMessage: ' + assertion.message.magenta);
52 }
53 stack = assertion.error.stack.replace(/ {4}(at)/g, ' $1');
54 stack = stack.replace(/:(.*?\n)/, '$1'.magenta);
55 grunt.log.error(stack + '\n').writeln();
56 }
57 }
58
59 // Define our own Nodeunit reporter.
60 nodeunit.reporters.grunt = {
61 info: 'Grunt reporter',
62 run: function(files, options, callback) {
63 var opts = {
64 // No idea.
65 testspec: undefined,
66 // Executed when the first test in a file is run. If no tests exist in
67 // the file, this doesn't execute.
68 moduleStart: function(name) {
69 // Keep track of this so that moduleDone output can be suppressed in
70 // cases where a test file contains no tests.
71 currentModule = name;
72 grunt.verbose.subhead('Testing ' + name).or.write('Testing ' + name);
73 },
74 // Executed after a file is done being processed. This executes whether
75 // tests exist in the file or not.
76 moduleDone: function(name) {
77 // Abort if no tests actually ran.
78 if (name !== currentModule) { return; }
79 // Print assertion errors here, if verbose mode is disabled.
80 if (!grunt.option('verbose')) {
81 if (failedAssertions.length > 0) {
82 grunt.log.writeln();
83 logFailedAssertions();
84 } else {
85 grunt.log.ok();
86 }
87 }
88 },
89 // Executed before each test is run.
90 testStart: function(name) {
91 // Keep track of the current test, in case test.done() was omitted
92 // and Nodeunit explodes.
93 unfinished[name] = name;
94 grunt.verbose.write(name + '...');
95 // Mute output, in cases where a function being tested logs through
96 // grunt (for testing grunt internals).
97 grunt.log.muted = true;
98 },
99 // Executed after each test and all its assertions are run.
100 testDone: function(name, assertions) {
101 delete unfinished[name];
102 // Un-mute output.
103 grunt.log.muted = false;
104 // Log errors if necessary, otherwise success.
105 if (assertions.failures()) {
106 assertions.forEach(function(ass) {
107 if (ass.failed()) {
108 ass.testName = name;
109 failedAssertions.push(ass);
110 }
111 });
112 if (grunt.option('verbose')) {
113 grunt.log.error();
114 logFailedAssertions();
115 } else {
116 grunt.log.write('F'.red);
117 }
118 } else {
119 grunt.verbose.ok().or.write('.');
120 }
121 },
122 // Executed when everything is all done.
123 done: function (assertions) {
124 if (assertions.failures()) {
125 grunt.warn(assertions.failures() + '/' + assertions.length +
126 ' assertions failed (' + assertions.duration + 'ms)',
127 Math.min(99, 90 + assertions.failures()));
128 } else {
129 grunt.verbose.writeln();
130 grunt.log.ok(assertions.length + ' assertions passed (' +
131 assertions.duration + 'ms)');
132 }
133 // Tell the task manager we're all done.
134 callback(); // callback(assertions.failures() === 0);
135 }
136 };
137
138 // Nodeunit needs absolute paths.
139 var paths = files.map(function(filepath) {
140 return path.resolve(filepath);
141 });
142 nodeunit.runFiles(paths, opts);
143 }
144 };
145
146 // ==========================================================================
147 // TASKS
148 // ==========================================================================
149
150 grunt.registerMultiTask('test', 'Run unit tests with nodeunit.', function() {
151 // File paths.
152 var filepaths = grunt.file.expandFiles(this.file.src);
153 // Clear all tests' cached require data, in case this task is run inside a
154 // "watch" task loop.
155 grunt.file.clearRequireCache(filepaths);
156 // Run test(s)... asynchronously!
157 nodeunit.reporters.grunt.run(filepaths, {}, this.async());
158 });
159
160};