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