source: Dev/trunk/node_modules/mocha/lib/reporters/html.js

Last change on this file was 484, checked in by hendrikvanantwerpen, 11 years ago

Commit node_modules, to make checkouts and builds more deterministic.

File size: 7.0 KB
Line 
1
2/**
3 * Module dependencies.
4 */
5
6var Base = require('./base')
7  , utils = require('../utils')
8  , Progress = require('../browser/progress')
9  , escape = utils.escape;
10
11/**
12 * Save timer references to avoid Sinon interfering (see GH-237).
13 */
14
15var Date = global.Date
16  , setTimeout = global.setTimeout
17  , setInterval = global.setInterval
18  , clearTimeout = global.clearTimeout
19  , clearInterval = global.clearInterval;
20
21/**
22 * Expose `HTML`.
23 */
24
25exports = module.exports = HTML;
26
27/**
28 * Stats template.
29 */
30
31var statsTemplate = '<ul id="mocha-stats">'
32  + '<li class="progress"><canvas width="40" height="40"></canvas></li>'
33  + '<li class="passes"><a href="#">passes:</a> <em>0</em></li>'
34  + '<li class="failures"><a href="#">failures:</a> <em>0</em></li>'
35  + '<li class="duration">duration: <em>0</em>s</li>'
36  + '</ul>';
37
38/**
39 * Initialize a new `HTML` reporter.
40 *
41 * @param {Runner} runner
42 * @api public
43 */
44
45function HTML(runner, root) {
46  Base.call(this, runner);
47
48  var self = this
49    , stats = this.stats
50    , total = runner.total
51    , stat = fragment(statsTemplate)
52    , items = stat.getElementsByTagName('li')
53    , passes = items[1].getElementsByTagName('em')[0]
54    , passesLink = items[1].getElementsByTagName('a')[0]
55    , failures = items[2].getElementsByTagName('em')[0]
56    , failuresLink = items[2].getElementsByTagName('a')[0]
57    , duration = items[3].getElementsByTagName('em')[0]
58    , canvas = stat.getElementsByTagName('canvas')[0]
59    , report = fragment('<ul id="mocha-report"></ul>')
60    , stack = [report]
61    , progress
62    , ctx
63
64  root = root || document.getElementById('mocha');
65
66  if (canvas.getContext) {
67    var ratio = window.devicePixelRatio || 1;
68    canvas.style.width = canvas.width;
69    canvas.style.height = canvas.height;
70    canvas.width *= ratio;
71    canvas.height *= ratio;
72    ctx = canvas.getContext('2d');
73    ctx.scale(ratio, ratio);
74    progress = new Progress;
75  }
76
77  if (!root) return error('#mocha div missing, add it to your document');
78
79  // pass toggle
80  on(passesLink, 'click', function(){
81    unhide();
82    var name = /pass/.test(report.className) ? '' : ' pass';
83    report.className = report.className.replace(/fail|pass/g, '') + name;
84    if (report.className.trim()) hideSuitesWithout('test pass');
85  });
86
87  // failure toggle
88  on(failuresLink, 'click', function(){
89    unhide();
90    var name = /fail/.test(report.className) ? '' : ' fail';
91    report.className = report.className.replace(/fail|pass/g, '') + name;
92    if (report.className.trim()) hideSuitesWithout('test fail');
93  });
94
95  root.appendChild(stat);
96  root.appendChild(report);
97
98  if (progress) progress.size(40);
99
100  runner.on('suite', function(suite){
101    if (suite.root) return;
102
103    // suite
104    var url = self.suiteURL(suite);
105    var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, escape(suite.title));
106
107    // container
108    stack[0].appendChild(el);
109    stack.unshift(document.createElement('ul'));
110    el.appendChild(stack[0]);
111  });
112
113  runner.on('suite end', function(suite){
114    if (suite.root) return;
115    stack.shift();
116  });
117
118  runner.on('fail', function(test, err){
119    if ('hook' == test.type) runner.emit('test end', test);
120  });
121
122  runner.on('test end', function(test){
123    // TODO: add to stats
124    var percent = stats.tests / this.total * 100 | 0;
125    if (progress) progress.update(percent).draw(ctx);
126
127    // update stats
128    var ms = new Date - stats.start;
129    text(passes, stats.passes);
130    text(failures, stats.failures);
131    text(duration, (ms / 1000).toFixed(2));
132
133    // test
134    if ('passed' == test.state) {
135      var url = self.testURL(test);
136      var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url);
137    } else if (test.pending) {
138      var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
139    } else {
140      var el = fragment('<li class="test fail"><h2>%e <a href="?grep=%e" class="replay">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle()));
141      var str = test.err.stack || test.err.toString();
142
143      // FF / Opera do not add the message
144      if (!~str.indexOf(test.err.message)) {
145        str = test.err.message + '\n' + str;
146      }
147
148      // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
149      // check for the result of the stringifying.
150      if ('[object Error]' == str) str = test.err.message;
151
152      // Safari doesn't give you a stack. Let's at least provide a source line.
153      if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) {
154        str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")";
155      }
156
157      el.appendChild(fragment('<pre class="error">%e</pre>', str));
158    }
159
160    // toggle code
161    // TODO: defer
162    if (!test.pending) {
163      var h2 = el.getElementsByTagName('h2')[0];
164
165      on(h2, 'click', function(){
166        pre.style.display = 'none' == pre.style.display
167          ? 'block'
168          : 'none';
169      });
170
171      var pre = fragment('<pre><code>%e</code></pre>', utils.clean(test.fn.toString()));
172      el.appendChild(pre);
173      pre.style.display = 'none';
174    }
175
176    // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
177    if (stack[0]) stack[0].appendChild(el);
178  });
179}
180
181/**
182 * Provide suite URL
183 *
184 * @param {Object} [suite]
185 */
186
187HTML.prototype.suiteURL = function(suite){
188  return '?grep=' + encodeURIComponent(suite.fullTitle());
189};
190
191/**
192 * Provide test URL
193 *
194 * @param {Object} [test]
195 */
196
197HTML.prototype.testURL = function(test){
198  return '?grep=' + encodeURIComponent(test.fullTitle());
199};
200
201/**
202 * Display error `msg`.
203 */
204
205function error(msg) {
206  document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
207}
208
209/**
210 * Return a DOM fragment from `html`.
211 */
212
213function fragment(html) {
214  var args = arguments
215    , div = document.createElement('div')
216    , i = 1;
217
218  div.innerHTML = html.replace(/%([se])/g, function(_, type){
219    switch (type) {
220      case 's': return String(args[i++]);
221      case 'e': return escape(args[i++]);
222    }
223  });
224
225  return div.firstChild;
226}
227
228/**
229 * Check for suites that do not have elements
230 * with `classname`, and hide them.
231 */
232
233function hideSuitesWithout(classname) {
234  var suites = document.getElementsByClassName('suite');
235  for (var i = 0; i < suites.length; i++) {
236    var els = suites[i].getElementsByClassName(classname);
237    if (0 == els.length) suites[i].className += ' hidden';
238  }
239}
240
241/**
242 * Unhide .hidden suites.
243 */
244
245function unhide() {
246  var els = document.getElementsByClassName('suite hidden');
247  for (var i = 0; i < els.length; ++i) {
248    els[i].className = els[i].className.replace('suite hidden', 'suite');
249  }
250}
251
252/**
253 * Set `el` text to `str`.
254 */
255
256function text(el, str) {
257  if (el.textContent) {
258    el.textContent = str;
259  } else {
260    el.innerText = str;
261  }
262}
263
264/**
265 * Listen on `event` with callback `fn`.
266 */
267
268function on(el, event, fn) {
269  if (el.addEventListener) {
270    el.addEventListener(event, fn, false);
271  } else {
272    el.attachEvent('on' + event, fn);
273  }
274}
Note: See TracBrowser for help on using the repository browser.