1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | module.exports = function(grunt) {
|
11 |
|
12 |
|
13 | var fs = require('fs');
|
14 | var path = require('path');
|
15 |
|
16 |
|
17 | var Tempfile = require('temporary/lib/file');
|
18 |
|
19 |
|
20 | var currentModule, currentTest, status;
|
21 |
|
22 | var unfinished = {};
|
23 |
|
24 |
|
25 | function formatMessage(str) {
|
26 | return String(str).split('\n').map(function(s) { return s.magenta; }).join('\n');
|
27 | }
|
28 |
|
29 |
|
30 | var failedAssertions = [];
|
31 | function logFailedAssertions() {
|
32 | var assertion;
|
33 |
|
34 | while (assertion = failedAssertions.shift()) {
|
35 | grunt.verbose.or.error(assertion.testName);
|
36 | grunt.log.error('Message: ' + formatMessage(assertion.message));
|
37 | if (assertion.actual !== assertion.expected) {
|
38 | grunt.log.error('Actual: ' + formatMessage(assertion.actual));
|
39 | grunt.log.error('Expected: ' + formatMessage(assertion.expected));
|
40 | }
|
41 | if (assertion.source) {
|
42 | grunt.log.error(assertion.source.replace(/ {4}(at)/g, ' $1'));
|
43 | }
|
44 | grunt.log.writeln();
|
45 | }
|
46 | }
|
47 |
|
48 |
|
49 | var phantomHandlers = {
|
50 |
|
51 | moduleStart: function(name) {
|
52 | unfinished[name] = true;
|
53 | currentModule = name;
|
54 | },
|
55 | moduleDone: function(name, failed, passed, total) {
|
56 | delete unfinished[name];
|
57 | },
|
58 | log: function(result, actual, expected, message, source) {
|
59 | if (!result) {
|
60 | failedAssertions.push({
|
61 | actual: actual, expected: expected, message: message, source: source,
|
62 | testName: currentTest
|
63 | });
|
64 | }
|
65 | },
|
66 | testStart: function(name) {
|
67 | currentTest = (currentModule ? currentModule + ' - ' : '') + name;
|
68 | grunt.verbose.write(currentTest + '...');
|
69 | },
|
70 | testDone: function(name, failed, passed, total) {
|
71 |
|
72 | if (failed > 0) {
|
73 |
|
74 | if (grunt.option('verbose')) {
|
75 | grunt.log.error();
|
76 | logFailedAssertions();
|
77 | } else {
|
78 | grunt.log.write('F'.red);
|
79 | }
|
80 | } else {
|
81 | grunt.verbose.ok().or.write('.');
|
82 | }
|
83 | },
|
84 | done: function(failed, passed, total, duration) {
|
85 | status.failed += failed;
|
86 | status.passed += passed;
|
87 | status.total += total;
|
88 | status.duration += duration;
|
89 |
|
90 | if (!grunt.option('verbose')) {
|
91 | if (failed > 0) {
|
92 | grunt.log.writeln();
|
93 | logFailedAssertions();
|
94 | } else {
|
95 | grunt.log.ok();
|
96 | }
|
97 | }
|
98 | },
|
99 |
|
100 | done_fail: function(url) {
|
101 | grunt.verbose.write('Running PhantomJS...').or.write('...');
|
102 | grunt.log.error();
|
103 | grunt.warn('PhantomJS unable to load "' + url + '" URI.', 90);
|
104 | },
|
105 | done_timeout: function() {
|
106 | grunt.log.writeln();
|
107 | grunt.warn('PhantomJS timed out, possibly due to a missing QUnit start() call.', 90);
|
108 | },
|
109 |
|
110 | console: console.log.bind(console),
|
111 |
|
112 | debug: grunt.log.debug.bind(grunt.log, 'phantomjs')
|
113 | };
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | grunt.registerMultiTask('qunit', 'Run QUnit unit tests in a headless PhantomJS instance.', function() {
|
120 |
|
121 | var urls = grunt.file.expandFileURLs(this.file.src);
|
122 |
|
123 |
|
124 | var done = this.async();
|
125 |
|
126 |
|
127 | status = {failed: 0, passed: 0, total: 0, duration: 0};
|
128 |
|
129 |
|
130 | grunt.utils.async.forEachSeries(urls, function(url, next) {
|
131 | var basename = path.basename(url);
|
132 | grunt.verbose.subhead('Testing ' + basename).or.write('Testing ' + basename);
|
133 |
|
134 |
|
135 | var tempfile = new Tempfile();
|
136 |
|
137 | var id;
|
138 |
|
139 | var n = 0;
|
140 |
|
141 |
|
142 | currentModule = null;
|
143 |
|
144 |
|
145 | function cleanup() {
|
146 | clearTimeout(id);
|
147 | tempfile.unlink();
|
148 | }
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | (function loopy() {
|
155 |
|
156 | grunt.log.muted = true;
|
157 |
|
158 | var lines = grunt.file.read(tempfile.path).split('\n').slice(0, -1);
|
159 |
|
160 | grunt.log.muted = false;
|
161 |
|
162 | var done = lines.slice(n).some(function(line) {
|
163 |
|
164 | var args = JSON.parse(line);
|
165 | var method = args.shift();
|
166 |
|
167 | if (phantomHandlers[method]) {
|
168 | phantomHandlers[method].apply(null, args);
|
169 | }
|
170 |
|
171 |
|
172 |
|
173 | return (/^done/).test(method);
|
174 | });
|
175 |
|
176 | if (done) {
|
177 |
|
178 | cleanup();
|
179 | next();
|
180 | } else {
|
181 |
|
182 | n = lines.length;
|
183 |
|
184 | id = setTimeout(loopy, 100);
|
185 | }
|
186 | }());
|
187 |
|
188 |
|
189 | grunt.helper('phantomjs', {
|
190 | code: 90,
|
191 | args: [
|
192 |
|
193 | '--config=' + grunt.task.getFile('qunit/phantom.json'),
|
194 |
|
195 | grunt.task.getFile('qunit/phantom.js'),
|
196 |
|
197 | tempfile.path,
|
198 |
|
199 | grunt.task.getFile('qunit/qunit.js'),
|
200 |
|
201 | url
|
202 | ],
|
203 | done: function(err) {
|
204 | if (err) {
|
205 | cleanup();
|
206 | done();
|
207 | }
|
208 | },
|
209 | });
|
210 | }, function(err) {
|
211 |
|
212 |
|
213 |
|
214 | if (status.failed > 0) {
|
215 | grunt.warn(status.failed + '/' + status.total + ' assertions failed (' +
|
216 | status.duration + 'ms)', Math.min(99, 90 + status.failed));
|
217 | } else {
|
218 | grunt.verbose.writeln();
|
219 | grunt.log.ok(status.total + ' assertions passed (' + status.duration + 'ms)');
|
220 | }
|
221 |
|
222 |
|
223 | done();
|
224 | });
|
225 | });
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 | grunt.registerHelper('phantomjs', function(options) {
|
232 | return grunt.utils.spawn({
|
233 | cmd: 'phantomjs',
|
234 | args: options.args
|
235 | }, function(err, result, code) {
|
236 | if (!err) { return options.done(null); }
|
237 |
|
238 | grunt.verbose.or.writeln();
|
239 | grunt.log.write('Running PhantomJS...').error();
|
240 | if (code === 127) {
|
241 | grunt.log.errorlns(
|
242 | 'In order for this task to work properly, PhantomJS must be ' +
|
243 | 'installed and in the system PATH (if you can run "phantomjs" at' +
|
244 | ' the command line, this task should work). Unfortunately, ' +
|
245 | 'PhantomJS cannot be installed automatically via npm or grunt. ' +
|
246 | 'See the grunt FAQ for PhantomJS installation instructions: ' +
|
247 | 'https://github.com/gruntjs/grunt/blob/master/docs/faq.md'
|
248 | );
|
249 | grunt.warn('PhantomJS not found.', options.code);
|
250 | } else {
|
251 | result.split('\n').forEach(grunt.log.error, grunt.log);
|
252 | grunt.warn('PhantomJS exited unexpectedly with exit code ' + code + '.', options.code);
|
253 | }
|
254 | options.done(code);
|
255 | });
|
256 | });
|
257 |
|
258 | };
|