1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | var Base = require('./base');
|
12 | var utils = require('../utils');
|
13 | var Progress = require('../browser/progress');
|
14 | var escapeRe = require('escape-string-regexp');
|
15 | var constants = require('../runner').constants;
|
16 | var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
|
17 | var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
|
18 | var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN;
|
19 | var EVENT_SUITE_END = constants.EVENT_SUITE_END;
|
20 | var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING;
|
21 | var escape = utils.escape;
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | var Date = global.Date;
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | exports = module.exports = HTML;
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | var statsTemplate =
|
40 | '<ul id="mocha-stats">' +
|
41 | '<li class="progress"><canvas width="40" height="40"></canvas></li>' +
|
42 | '<li class="passes"><a href="javascript:void(0);">passes:</a> <em>0</em></li>' +
|
43 | '<li class="failures"><a href="javascript:void(0);">failures:</a> <em>0</em></li>' +
|
44 | '<li class="duration">duration: <em>0</em>s</li>' +
|
45 | '</ul>';
|
46 |
|
47 | var playIcon = '‣';
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | function HTML(runner, options) {
|
60 | Base.call(this, runner, options);
|
61 |
|
62 | var self = this;
|
63 | var stats = this.stats;
|
64 | var stat = fragment(statsTemplate);
|
65 | var items = stat.getElementsByTagName('li');
|
66 | var passes = items[1].getElementsByTagName('em')[0];
|
67 | var passesLink = items[1].getElementsByTagName('a')[0];
|
68 | var failures = items[2].getElementsByTagName('em')[0];
|
69 | var failuresLink = items[2].getElementsByTagName('a')[0];
|
70 | var duration = items[3].getElementsByTagName('em')[0];
|
71 | var canvas = stat.getElementsByTagName('canvas')[0];
|
72 | var report = fragment('<ul id="mocha-report"></ul>');
|
73 | var stack = [report];
|
74 | var progress;
|
75 | var ctx;
|
76 | var root = document.getElementById('mocha');
|
77 |
|
78 | if (canvas.getContext) {
|
79 | var ratio = window.devicePixelRatio || 1;
|
80 | canvas.style.width = canvas.width;
|
81 | canvas.style.height = canvas.height;
|
82 | canvas.width *= ratio;
|
83 | canvas.height *= ratio;
|
84 | ctx = canvas.getContext('2d');
|
85 | ctx.scale(ratio, ratio);
|
86 | progress = new Progress();
|
87 | }
|
88 |
|
89 | if (!root) {
|
90 | return error('#mocha div missing, add it to your document');
|
91 | }
|
92 |
|
93 |
|
94 | on(passesLink, 'click', function(evt) {
|
95 | evt.preventDefault();
|
96 | unhide();
|
97 | var name = /pass/.test(report.className) ? '' : ' pass';
|
98 | report.className = report.className.replace(/fail|pass/g, '') + name;
|
99 | if (report.className.trim()) {
|
100 | hideSuitesWithout('test pass');
|
101 | }
|
102 | });
|
103 |
|
104 |
|
105 | on(failuresLink, 'click', function(evt) {
|
106 | evt.preventDefault();
|
107 | unhide();
|
108 | var name = /fail/.test(report.className) ? '' : ' fail';
|
109 | report.className = report.className.replace(/fail|pass/g, '') + name;
|
110 | if (report.className.trim()) {
|
111 | hideSuitesWithout('test fail');
|
112 | }
|
113 | });
|
114 |
|
115 | root.appendChild(stat);
|
116 | root.appendChild(report);
|
117 |
|
118 | if (progress) {
|
119 | progress.size(40);
|
120 | }
|
121 |
|
122 | runner.on(EVENT_SUITE_BEGIN, function(suite) {
|
123 | if (suite.root) {
|
124 | return;
|
125 | }
|
126 |
|
127 |
|
128 | var url = self.suiteURL(suite);
|
129 | var el = fragment(
|
130 | '<li class="suite"><h1><a href="%s">%s</a></h1></li>',
|
131 | url,
|
132 | escape(suite.title)
|
133 | );
|
134 |
|
135 |
|
136 | stack[0].appendChild(el);
|
137 | stack.unshift(document.createElement('ul'));
|
138 | el.appendChild(stack[0]);
|
139 | });
|
140 |
|
141 | runner.on(EVENT_SUITE_END, function(suite) {
|
142 | if (suite.root) {
|
143 | updateStats();
|
144 | return;
|
145 | }
|
146 | stack.shift();
|
147 | });
|
148 |
|
149 | runner.on(EVENT_TEST_PASS, function(test) {
|
150 | var url = self.testURL(test);
|
151 | var markup =
|
152 | '<li class="test pass %e"><h2>%e<span class="duration">%ems</span> ' +
|
153 | '<a href="%s" class="replay">' +
|
154 | playIcon +
|
155 | '</a></h2></li>';
|
156 | var el = fragment(markup, test.speed, test.title, test.duration, url);
|
157 | self.addCodeToggle(el, test.body);
|
158 | appendToStack(el);
|
159 | updateStats();
|
160 | });
|
161 |
|
162 | runner.on(EVENT_TEST_FAIL, function(test) {
|
163 | var el = fragment(
|
164 | '<li class="test fail"><h2>%e <a href="%e" class="replay">' +
|
165 | playIcon +
|
166 | '</a></h2></li>',
|
167 | test.title,
|
168 | self.testURL(test)
|
169 | );
|
170 | var stackString;
|
171 | var message = test.err.toString();
|
172 |
|
173 |
|
174 |
|
175 | if (message === '[object Error]') {
|
176 | message = test.err.message;
|
177 | }
|
178 |
|
179 | if (test.err.stack) {
|
180 | var indexOfMessage = test.err.stack.indexOf(test.err.message);
|
181 | if (indexOfMessage === -1) {
|
182 | stackString = test.err.stack;
|
183 | } else {
|
184 | stackString = test.err.stack.substr(
|
185 | test.err.message.length + indexOfMessage
|
186 | );
|
187 | }
|
188 | } else if (test.err.sourceURL && test.err.line !== undefined) {
|
189 |
|
190 | stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')';
|
191 | }
|
192 |
|
193 | stackString = stackString || '';
|
194 |
|
195 | if (test.err.htmlMessage && stackString) {
|
196 | el.appendChild(
|
197 | fragment(
|
198 | '<div class="html-error">%s\n<pre class="error">%e</pre></div>',
|
199 | test.err.htmlMessage,
|
200 | stackString
|
201 | )
|
202 | );
|
203 | } else if (test.err.htmlMessage) {
|
204 | el.appendChild(
|
205 | fragment('<div class="html-error">%s</div>', test.err.htmlMessage)
|
206 | );
|
207 | } else {
|
208 | el.appendChild(
|
209 | fragment('<pre class="error">%e%e</pre>', message, stackString)
|
210 | );
|
211 | }
|
212 |
|
213 | self.addCodeToggle(el, test.body);
|
214 | appendToStack(el);
|
215 | updateStats();
|
216 | });
|
217 |
|
218 | runner.on(EVENT_TEST_PENDING, function(test) {
|
219 | var el = fragment(
|
220 | '<li class="test pass pending"><h2>%e</h2></li>',
|
221 | test.title
|
222 | );
|
223 | appendToStack(el);
|
224 | updateStats();
|
225 | });
|
226 |
|
227 | function appendToStack(el) {
|
228 |
|
229 | if (stack[0]) {
|
230 | stack[0].appendChild(el);
|
231 | }
|
232 | }
|
233 |
|
234 | function updateStats() {
|
235 |
|
236 | var percent = ((stats.tests / runner.total) * 100) | 0;
|
237 | if (progress) {
|
238 | progress.update(percent).draw(ctx);
|
239 | }
|
240 |
|
241 |
|
242 | var ms = new Date() - stats.start;
|
243 | text(passes, stats.passes);
|
244 | text(failures, stats.failures);
|
245 | text(duration, (ms / 1000).toFixed(2));
|
246 | }
|
247 | }
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | function makeUrl(s) {
|
256 | var search = window.location.search;
|
257 |
|
258 |
|
259 | if (search) {
|
260 | search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
|
261 | }
|
262 |
|
263 | return (
|
264 | window.location.pathname +
|
265 | (search ? search + '&' : '?') +
|
266 | 'grep=' +
|
267 | encodeURIComponent(escapeRe(s))
|
268 | );
|
269 | }
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 | HTML.prototype.suiteURL = function(suite) {
|
277 | return makeUrl(suite.fullTitle());
|
278 | };
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 | HTML.prototype.testURL = function(test) {
|
286 | return makeUrl(test.fullTitle());
|
287 | };
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 | HTML.prototype.addCodeToggle = function(el, contents) {
|
296 | var h2 = el.getElementsByTagName('h2')[0];
|
297 |
|
298 | on(h2, 'click', function() {
|
299 | pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
|
300 | });
|
301 |
|
302 | var pre = fragment('<pre><code>%e</code></pre>', utils.clean(contents));
|
303 | el.appendChild(pre);
|
304 | pre.style.display = 'none';
|
305 | };
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 | function error(msg) {
|
313 | document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
|
314 | }
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 | function fragment(html) {
|
322 | var args = arguments;
|
323 | var div = document.createElement('div');
|
324 | var i = 1;
|
325 |
|
326 | div.innerHTML = html.replace(/%([se])/g, function(_, type) {
|
327 | switch (type) {
|
328 | case 's':
|
329 | return String(args[i++]);
|
330 | case 'e':
|
331 | return escape(args[i++]);
|
332 |
|
333 | }
|
334 | });
|
335 |
|
336 | return div.firstChild;
|
337 | }
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 | function hideSuitesWithout(classname) {
|
346 | var suites = document.getElementsByClassName('suite');
|
347 | for (var i = 0; i < suites.length; i++) {
|
348 | var els = suites[i].getElementsByClassName(classname);
|
349 | if (!els.length) {
|
350 | suites[i].className += ' hidden';
|
351 | }
|
352 | }
|
353 | }
|
354 |
|
355 |
|
356 |
|
357 |
|
358 | function unhide() {
|
359 | var els = document.getElementsByClassName('suite hidden');
|
360 | while (els.length > 0) {
|
361 | els[0].className = els[0].className.replace('suite hidden', 'suite');
|
362 | }
|
363 | }
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 | function text(el, contents) {
|
372 | if (el.textContent) {
|
373 | el.textContent = contents;
|
374 | } else {
|
375 | el.innerText = contents;
|
376 | }
|
377 | }
|
378 |
|
379 |
|
380 |
|
381 |
|
382 | function on(el, event, fn) {
|
383 | if (el.addEventListener) {
|
384 | el.addEventListener(event, fn, false);
|
385 | } else {
|
386 | el.attachEvent('on' + event, fn);
|
387 | }
|
388 | }
|
389 |
|
390 | HTML.browserOnly = true;
|