• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mochajs / mocha / 5329 / 7

Source File

6.93
/lib/reporters/html.js
1
'use strict';
2

3
/* eslint-env browser */
4
/**
5
 * @module HTML
6
 */
7
/**
8
 * Module dependencies.
9
 */
10

11
var Base = require('./base');
247✔
12
var utils = require('../utils');
247✔
13
var Progress = require('../browser/progress');
247✔
14
var escapeRe = require('escape-string-regexp');
247✔
15
var Runner = require('../runner');
247✔
16
var escape = utils.escape;
247✔
17

18
/**
19
 * Save timer references to avoid Sinon interfering (see GH-237).
20
 */
21

22
var Date = global.Date;
247✔
23

24
/**
25
 * Expose `HTML`.
26
 */
27

28
exports = module.exports = HTML;
247✔
29

30
/**
31
 * Stats template.
32
 */
33

34
var statsTemplate =
35
  '<ul id="mocha-stats">' +
247✔
36
  '<li class="progress"><canvas width="40" height="40"></canvas></li>' +
37
  '<li class="passes"><a href="javascript:void(0);">passes:</a> <em>0</em></li>' +
38
  '<li class="failures"><a href="javascript:void(0);">failures:</a> <em>0</em></li>' +
39
  '<li class="duration">duration: <em>0</em>s</li>' +
40
  '</ul>';
41

42
var playIcon = '&#x2023;';
247✔
43

44
/**
45
 * Initialize a new `HTML` reporter.
46
 *
47
 * @public
48
 * @class
49
 * @memberof Mocha.reporters
50
 * @extends Mocha.reporters.Base
51
 * @param {Runner} runner
52
 */
53
function HTML(runner) {
54
  Base.call(this, runner);
×
55

56
  var self = this;
×
57
  var stats = this.stats;
×
58
  var stat = fragment(statsTemplate);
×
59
  var items = stat.getElementsByTagName('li');
×
60
  var passes = items[1].getElementsByTagName('em')[0];
×
61
  var passesLink = items[1].getElementsByTagName('a')[0];
×
62
  var failures = items[2].getElementsByTagName('em')[0];
×
63
  var failuresLink = items[2].getElementsByTagName('a')[0];
×
64
  var duration = items[3].getElementsByTagName('em')[0];
×
65
  var canvas = stat.getElementsByTagName('canvas')[0];
×
66
  var report = fragment('<ul id="mocha-report"></ul>');
×
67
  var stack = [report];
×
68
  var progress;
69
  var ctx;
70
  var root = document.getElementById('mocha');
×
71

72
  if (canvas.getContext) {
×
73
    var ratio = window.devicePixelRatio || 1;
×
74
    canvas.style.width = canvas.width;
×
75
    canvas.style.height = canvas.height;
×
76
    canvas.width *= ratio;
×
77
    canvas.height *= ratio;
×
78
    ctx = canvas.getContext('2d');
×
79
    ctx.scale(ratio, ratio);
×
80
    progress = new Progress();
×
81
  }
82

83
  if (!root) {
×
84
    return error('#mocha div missing, add it to your document');
×
85
  }
86

87
  // pass toggle
88
  on(passesLink, 'click', function(evt) {
×
89
    evt.preventDefault();
×
90
    unhide();
×
91
    var name = /pass/.test(report.className) ? '' : ' pass';
×
92
    report.className = report.className.replace(/fail|pass/g, '') + name;
×
93
    if (report.className.trim()) {
×
94
      hideSuitesWithout('test pass');
×
95
    }
96
  });
97

98
  // failure toggle
99
  on(failuresLink, 'click', function(evt) {
×
100
    evt.preventDefault();
×
101
    unhide();
×
102
    var name = /fail/.test(report.className) ? '' : ' fail';
×
103
    report.className = report.className.replace(/fail|pass/g, '') + name;
×
104
    if (report.className.trim()) {
×
105
      hideSuitesWithout('test fail');
×
106
    }
107
  });
108

109
  root.appendChild(stat);
×
110
  root.appendChild(report);
×
111

112
  if (progress) {
×
113
    progress.size(40);
×
114
  }
115

116
  runner.on(Runner.constants.RUNNER_EVENT_SUITE, function(suite) {
×
117
    if (suite.root) {
×
118
      return;
×
119
    }
120

121
    // suite
122
    var url = self.suiteURL(suite);
×
123
    var el = fragment(
×
124
      '<li class="suite"><h1><a href="%s">%s</a></h1></li>',
125
      url,
126
      escape(suite.title)
127
    );
128

129
    // container
130
    stack[0].appendChild(el);
×
131
    stack.unshift(document.createElement('ul'));
×
132
    el.appendChild(stack[0]);
×
133
  });
134

135
  runner.on('suite end', function(suite) {
×
136
    if (suite.root) {
×
137
      updateStats();
×
138
      return;
×
139
    }
140
    stack.shift();
×
141
  });
142

143
  runner.on(Runner.constants.RUNNER_EVENT_PASS, function(test) {
×
144
    var url = self.testURL(test);
×
145
    var markup =
146
      '<li class="test pass %e"><h2>%e<span class="duration">%ems</span> ' +
×
147
      '<a href="%s" class="replay">' +
148
      playIcon +
149
      '</a></h2></li>';
150
    var el = fragment(markup, test.speed, test.title, test.duration, url);
×
151
    self.addCodeToggle(el, test.body);
×
152
    appendToStack(el);
×
153
    updateStats();
×
154
  });
155

156
  runner.on(Runner.constants.RUNNER_EVENT_FAIL, function(test) {
×
157
    var el = fragment(
×
158
      '<li class="test fail"><h2>%e <a href="%e" class="replay">' +
159
        playIcon +
160
        '</a></h2></li>',
161
      test.title,
162
      self.testURL(test)
163
    );
164
    var stackString; // Note: Includes leading newline
165
    var message = test.err.toString();
×
166

167
    // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
168
    // check for the result of the stringifying.
169
    if (message === '[object Error]') {
×
170
      message = test.err.message;
×
171
    }
172

173
    if (test.err.stack) {
×
174
      var indexOfMessage = test.err.stack.indexOf(test.err.message);
×
175
      if (indexOfMessage === -1) {
×
176
        stackString = test.err.stack;
×
177
      } else {
178
        stackString = test.err.stack.substr(
×
179
          test.err.message.length + indexOfMessage
180
        );
181
      }
182
    } else if (test.err.sourceURL && test.err.line !== undefined) {
×
183
      // Safari doesn't give you a stack. Let's at least provide a source line.
184
      stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')';
×
185
    }
186

187
    stackString = stackString || '';
×
188

189
    if (test.err.htmlMessage && stackString) {
×
190
      el.appendChild(
×
191
        fragment(
192
          '<div class="html-error">%s\n<pre class="error">%e</pre></div>',
193
          test.err.htmlMessage,
194
          stackString
195
        )
196
      );
197
    } else if (test.err.htmlMessage) {
×
198
      el.appendChild(
×
199
        fragment('<div class="html-error">%s</div>', test.err.htmlMessage)
200
      );
201
    } else {
202
      el.appendChild(
×
203
        fragment('<pre class="error">%e%e</pre>', message, stackString)
204
      );
205
    }
206

207
    self.addCodeToggle(el, test.body);
×
208
    appendToStack(el);
×
209
    updateStats();
×
210
  });
211

212
  runner.on(Runner.constants.RUNNER_EVENT_PENDING, function(test) {
×
213
    var el = fragment(
×
214
      '<li class="test pass pending"><h2>%e</h2></li>',
215
      test.title
216
    );
217
    appendToStack(el);
×
218
    updateStats();
×
219
  });
220

221
  function appendToStack(el) {
222
    // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
223
    if (stack[0]) {
×
224
      stack[0].appendChild(el);
×
225
    }
226
  }
227

228
  function updateStats() {
229
    // TODO: add to stats
230
    var percent = ((stats.tests / runner.total) * 100) | 0;
×
231
    if (progress) {
×
232
      progress.update(percent).draw(ctx);
×
233
    }
234

235
    // update stats
236
    var ms = new Date() - stats.start;
×
237
    text(passes, stats.passes);
×
238
    text(failures, stats.failures);
×
239
    text(duration, (ms / 1000).toFixed(2));
×
240
  }
241
}
242

243
/**
244
 * Makes a URL, preserving querystring ("search") parameters.
245
 *
246
 * @param {string} s
247
 * @return {string} A new URL.
248
 */
249
function makeUrl(s) {
250
  var search = window.location.search;
×
251

252
  // Remove previous grep query parameter if present
253
  if (search) {
×
254
    search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
×
255
  }
256

257
  return (
×
258
    window.location.pathname +
259
    (search ? search + '&' : '?') +
×
260
    'grep=' +
261
    encodeURIComponent(escapeRe(s))
262
  );
263
}
264

265
/**
266
 * Provide suite URL.
267
 *
268
 * @param {Object} [suite]
269
 */
270
HTML.prototype.suiteURL = function(suite) {
247✔
271
  return makeUrl(suite.fullTitle());
×
272
};
273

274
/**
275
 * Provide test URL.
276
 *
277
 * @param {Object} [test]
278
 */
279
HTML.prototype.testURL = function(test) {
247✔
280
  return makeUrl(test.fullTitle());
×
281
};
282

283
/**
284
 * Adds code toggle functionality for the provided test's list element.
285
 *
286
 * @param {HTMLLIElement} el
287
 * @param {string} contents
288
 */
289
HTML.prototype.addCodeToggle = function(el, contents) {
247✔
290
  var h2 = el.getElementsByTagName('h2')[0];
×
291

292
  on(h2, 'click', function() {
×
293
    pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
×
294
  });
295

296
  var pre = fragment('<pre><code>%e</code></pre>', utils.clean(contents));
×
297
  el.appendChild(pre);
×
298
  pre.style.display = 'none';
×
299
};
300

301
/**
302
 * Display error `msg`.
303
 *
304
 * @param {string} msg
305
 */
306
function error(msg) {
307
  document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
×
308
}
309

310
/**
311
 * Return a DOM fragment from `html`.
312
 *
313
 * @param {string} html
314
 */
315
function fragment(html) {
316
  var args = arguments;
×
317
  var div = document.createElement('div');
×
318
  var i = 1;
×
319

320
  div.innerHTML = html.replace(/%([se])/g, function(_, type) {
×
321
    switch (type) {
×
322
      case 's':
323
        return String(args[i++]);
×
324
      case 'e':
325
        return escape(args[i++]);
×
326
      // no default
327
    }
328
  });
329

330
  return div.firstChild;
×
331
}
332

333
/**
334
 * Check for suites that do not have elements
335
 * with `classname`, and hide them.
336
 *
337
 * @param {text} classname
338
 */
339
function hideSuitesWithout(classname) {
340
  var suites = document.getElementsByClassName('suite');
×
341
  for (var i = 0; i < suites.length; i++) {
×
342
    var els = suites[i].getElementsByClassName(classname);
×
343
    if (!els.length) {
×
344
      suites[i].className += ' hidden';
×
345
    }
346
  }
347
}
348

349
/**
350
 * Unhide .hidden suites.
351
 */
352
function unhide() {
353
  var els = document.getElementsByClassName('suite hidden');
×
354
  for (var i = 0; i < els.length; ++i) {
×
355
    els[i].className = els[i].className.replace('suite hidden', 'suite');
×
356
  }
357
}
358

359
/**
360
 * Set an element's text contents.
361
 *
362
 * @param {HTMLElement} el
363
 * @param {string} contents
364
 */
365
function text(el, contents) {
366
  if (el.textContent) {
×
367
    el.textContent = contents;
×
368
  } else {
369
    el.innerText = contents;
×
370
  }
371
}
372

373
/**
374
 * Listen on `event` with callback `fn`.
375
 */
376
function on(el, event, fn) {
377
  if (el.addEventListener) {
×
378
    el.addEventListener(event, fn, false);
×
379
  } else {
380
    el.attachEvent('on' + event, fn);
×
381
  }
382
}
383

384
HTML.browserOnly = true;
247✔
  • Back to Build 4883
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc