Coverage

47%
94
45
49

/home/ubuntu/src/github.com/dalekjs/dalek-reporter-html/index.js

47%
94
45
49
LineHitsSource
1/*!
2 *
3 * Copyright (c) 2013 Sebastian Golasch
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
251'use strict';
26
27// ext. libs
281var Handlebars = require('handlebars');
291var stylus = require('stylus');
301var fs = require('fs');
31
32// int. globals
331var reporter = null;
34
35/**
36 * The HTML reporter can produce a set of HTML files with the results of your testrun.
37 *
38 * The reporter can be installed with the following command:
39 *
40 * ```bash
41 * $ npm install dalek-reporter-html --save-dev
42 * ```
43 *
44 * By default the files will be written to the `report/dalek/` folder,
45 * you can change this by adding a config option to the your Dalekfile
46 *
47 * ```javascript
48 * "html-reporter": {
49 * "dest": "your/folder"
50 * }
51 * ```
52 *
53 * If you would like to use the reporter (in addition to the std. console reporter),
54 * you can start dalek with a special command line argument
55 *
56 * ```bash
57 * $ dalek your_test.js -r console,html
58 * ```
59 *
60 * or you can add it to your Dalekfile
61 *
62 * ```javascript
63 * "reporter": ["console", "html"]
64 * ```
65 *
66 * @class Reporter
67 * @constructor
68 * @part html
69 * @api
70 */
71
721function Reporter (opts) {
731 this.events = opts.events;
741 this.config = opts.config;
751 this.temporaryAssertions = [];
761 this.temp = {};
77
781 var defaultReportFolder = 'report/dalek';
791 this.dest = this.config.get('html-reporter') && this.config.get('html-reporter').dest ? this.config.get('html-reporter').dest : defaultReportFolder;
80
811 this.loadTemplates();
821 this.initOutputHandlers();
831 this.startListening();
84}
85
86/**
87 * @module Reporter
88 */
89
901module.exports = function (opts) {
911 if (reporter === null) {
921 reporter = new Reporter(opts);
93 }
94
951 return reporter;
96};
97
981Reporter.prototype = {
99
100 /**
101 * Inits the html buffer objects
102 *
103 * @method initOutputHandlers
104 * @chainable
105 */
106
107 initOutputHandlers: function () {
1081 this.output = {};
1091 this.output.test = {};
1101 return this;
111 },
112
113 /**
114 * Loads and prepares all the templates for
115 * CSS, JS & HTML
116 *
117 * @method loadTemplates
118 * @chainable
119 */
120
121 loadTemplates: function () {
122 // render stylesheets
1231 var precss = fs.readFileSync(__dirname + '/themes/default/styl/default.styl', 'utf8');
1241 stylus.render(precss, { filename: 'default.css' }, function(err, css){
1251 if (err) {
1260 throw err;
127 }
128
1291 this.styles = css;
130 }.bind(this));
131
132 // collect client js (to be inined later)
1331 this.js = fs.readFileSync(__dirname + '/themes/default/js/default.js', 'utf8');
134
135 // register handlebars helpers
1361 Handlebars.registerHelper('roundNumber', function (number) {
1370 return Math.round(number * Math.pow(10, 2)) / Math.pow(10, 2);
138 });
139
140 // collect & compile templates
1411 this.templates = {};
1421 this.templates.test = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/test.hbs', 'utf8'));
1431 this.templates.wrapper = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/wrapper.hbs', 'utf8'));
1441 this.templates.testresult = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/tests.hbs', 'utf8'));
1451 this.templates.banner = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/banner.hbs', 'utf8'));
1461 this.templates.detail = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/detail.hbs', 'utf8'));
147
1481 return this;
149 },
150
151 /**
152 * Connects to all the event listeners
153 *
154 * @method startListening
155 * @chainable
156 */
157
158 startListening: function () {
159 // index page
1601 this.events.on('report:assertion', this.outputAssertionResult.bind(this));
1611 this.events.on('report:test:finished', this.outputTestFinished.bind(this));
1621 this.events.on('report:runner:finished', this.outputRunnerFinished.bind(this));
1631 this.events.on('report:run:browser', this.outputRunBrowser.bind(this));
164
165 // detail page
1661 this.events.on('report:test:started', this.startDetailPage.bind(this));
1671 this.events.on('report:action', this.addActionToDetailPage.bind(this));
1681 this.events.on('report:assertion', this.addAssertionToDetailPage.bind(this));
1691 this.events.on('report:test:finished', this.finishDetailPage.bind(this));
170
1711 return this;
172 },
173
174 /**
175 * Prepares the output for a test detail page
176 *
177 * @method startDetailPage
178 * @chainable
179 */
180
181 startDetailPage: function () {
1820 this.detailContents = {};
1830 this.detailContents.eventLog = [];
1840 return this;
185 },
186
187 /**
188 * Adds an action output to the detail page
189 *
190 * @method addActionToDetailPage
191 * @param {object} data Event data
192 * @chainable
193 */
194
195 addActionToDetailPage: function (data) {
1960 data.isAction = true;
1970 this.detailContents.eventLog.push(data);
1980 return this;
199 },
200
201 /**
202 * Adds an assertion result to the detail page
203 *
204 * @method addAssertionToDetailPage
205 * @param {object} data Event data
206 * @chainable
207 */
208
209 addAssertionToDetailPage: function (data) {
2100 data.isAssertion = true;
2110 this.detailContents.eventLog.push(data);
2120 return this;
213 },
214
215 /**
216 * Writes a detail page to the file system
217 *
218 * @method finishDetailPage
219 * @param {object} data Event data
220 * @chainable
221 */
222
223 finishDetailPage: function (data) {
2240 this.detailContents.testResult = data;
2250 this.detailContents.styles = this.styles;
2260 this.detailContents.js = this.js;
2270 fs.writeFileSync(this.dest + '/details/' + data.id + '.html', this.templates.detail(this.detailContents), 'utf8');
2280 return this;
229 },
230
231 /**
232 * Stores the current browser name
233 *
234 * @method outputRunBrowser
235 * @param {string} browser Browser name
236 * @chainable
237 */
238
239 outputRunBrowser: function (browser) {
2400 this.temp.browser = browser;
2410 return this;
242 },
243
244 /**
245 * Writes the index page to the filesystem
246 *
247 * @method outputRunnerFinished
248 * @param {object} data Event data
249 * @chainable
250 */
251
252 outputRunnerFinished: function (data) {
2530 var body = '';
2540 var contents = '';
2550 var tests = '';
2560 var banner = '';
257
258 // add test results
2590 var keys = Object.keys(this.output.test);
2600 keys.forEach(function (key) {
2610 tests += this.output.test[key];
262 }.bind(this));
263
264 // compile the test result template
2650 body = this.templates.testresult({result: data, tests: tests});
266
267 // compile the banner
2680 banner = this.templates.banner({status: data.status});
269
270 // compile the contents within the wrapper template
2710 contents = this.templates.wrapper({styles: this.styles, js: this.js, banner: banner, body: body});
272
273 // save the main test output file
2740 this.events.emit('report:written', {type: 'html', dest: this.dest});
2750 this._recursiveMakeDirSync(this.dest + '/details');
2760 fs.writeFileSync(this.dest + '/index.html', contents, 'utf8');
2770 return this;
278 },
279
280 /**
281 * Pushes an assertion result to the index output queue
282 *
283 * @method outputAssertionResult
284 * @param {object} data Event data
285 * @chainable
286 */
287
288 outputAssertionResult: function (data) {
2890 this.temporaryAssertions.push(data);
2900 return this;
291 },
292
293 /**
294 * Pushes an test result to the index output queue
295 *
296 * @method outputTestFinished
297 * @param {object} data Event data
298 * @chainable
299 */
300
301 outputTestFinished: function (data) {
3020 data.assertionInfo = this.temporaryAssertions;
3030 data.browser = this.temp.browser;
3040 this.output.test[data.id] = this.templates.test(data);
3050 this.temporaryAssertions = [];
3060 return this;
307 },
308
309 /**
310 * Helper method to generate deeper nested directory structures
311 *
312 * @method _recursiveMakeDirSync
313 * @param {string} path PAth to create
314 */
315
316 _recursiveMakeDirSync: function (path) {
3170 var pathSep = require('path').sep;
3180 var dirs = path.split(pathSep);
3190 var root = '';
320
3210 while (dirs.length > 0) {
3220 var dir = dirs.shift();
3230 if (dir === '') {
3240 root = pathSep;
325 }
3260 if (!fs.existsSync(root + dir)) {
3270 fs.mkdirSync(root + dir);
328 }
3290 root += dir + pathSep;
330 }
331 }
332};
333