diff --git a/package.json b/package.json index 00a1460348a63..9881e46e77575 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "grunt-contrib-watch": "~0.5.3", "grunt-contrib-jade": "~0.10.0", "grunt-contrib-less": "~0.9.0", - "grunt-cli": "~0.1.13" + "grunt-cli": "~0.1.13", + "istanbul": "~0.2.4", + "path-browserify": "0.0.0" }, "scripts": { "test": "grunt test", diff --git a/src/courier/courier.js b/src/courier/courier.js index b38f1a2ceaa23..54d5588f61674 100644 --- a/src/courier/courier.js +++ b/src/courier/courier.js @@ -286,5 +286,17 @@ define(function (require) { this.fetch('doc'); }; + // get the list of open data source objects + // primarily for testing purposes + Courier.prototype._openSources = function (type) { + if (!type) { + return _.transform(this._refs, function (open, refs) { + [].push.apply(open, refs); + }, []); + } + + return this._refs[type] || []; + }; + return Courier; }); \ No newline at end of file diff --git a/src/kibana/require.config.js b/src/kibana/require.config.js index ec9cf26e87ea8..1dafaa38bf415 100644 --- a/src/kibana/require.config.js +++ b/src/kibana/require.config.js @@ -15,7 +15,8 @@ require.config({ lodash: '../bower_components/lodash/dist/lodash', moment: '../bower_components/moment/moment', gridster: '../bower_components/gridster/dist/jquery.gridster', - config: '../config' + configFile: '../config', + bower_components: '../bower_components' }, shim: { angular: { diff --git a/tasks/config/connect.js b/tasks/config/connect.js index 6bc3dde2a8878..b84c760a8225f 100644 --- a/tasks/config/connect.js +++ b/tasks/config/connect.js @@ -1,20 +1,59 @@ module.exports = function (grunt) { + var instrumentationMiddleware = require('../utils/instrumentation'); + var amdWrapMiddleware = require('../utils/amd-wrapper'); + return { dev: { options: { - base: '<%= src %>' - } - }, - test: { - options: { - base: [ - '<%= unitTestDir %>', - '<%= testUtilsDir %>', - '<%= src %>', - '<%= root %>/node_modules/mocha', - '<%= root %>/node_modules/expect.js' - ], - port: 8001 + middleware: function (connect, options, stack) { + stack = stack || []; + + var root = grunt.config.get('root'); + + // when a request for an intrumented file comes in (?instrument=true) + // and it is included in `pattern`, it will be handled + // by this middleware + stack.push(instrumentationMiddleware({ + // root that files should be served from + root: root, + + // make file names easier to read + displayRoot: grunt.config.get('src'), + + // filter the filenames that will be served + filter: function (filename) { + // return true if the filename should be + // included in the coverage report (results are cached) + return grunt.file.isMatch([ + '**/src/**/*.js', + '!**/src/bower_components/**/*', + '!**/src/kibana/utils/{event_emitter,next_tick}.js' + ], filename); + } + })); + + // minimize code duplication (especially in the istanbul reporter) + // by allowing node_modules to be requested in an AMD wrapper + stack.push(amdWrapMiddleware({ + root: root + })); + + // standard static middleware reading from the root + stack.push(connect.static(root)); + + // allow browsing directories + stack.push(connect.directory(root)); + + // redirect requests for '/' to '/src/' + stack.push(function (req, res, next) { + if (req.url !== '/') return next(); + res.statusCode = 303; + res.setHeader('Location', '/src/'); + res.end(); + }); + + return stack; + } } } }; diff --git a/tasks/config/jade.js b/tasks/config/jade.js index fd19f7f151e94..dc03d72e5c250 100644 --- a/tasks/config/jade.js +++ b/tasks/config/jade.js @@ -1,24 +1,41 @@ module.exports = function (grunt) { + var path = require('path'); + return { + options: { + compileDebug: false + }, test: { - src: [ - '<%= unitTestDir %>/**/*.jade', - '<%= app %>/partials/**/*.jade', - '<%= app %>/apps/**/*.jade' - ], - expand: true, - ext: '.html', + files: { + '<%= unitTestDir %>/index.html': '<%= unitTestDir %>/index.jade' + }, options: { data: function (src, dest) { - var pattern = grunt.config.process('<%= unitTestDir %>/**/*.js'); - var tests = grunt.file.expand({}, pattern).map(function (filename) { - return filename.replace(grunt.config.get('unitTestDir'), ''); - }); - return { tests: JSON.stringify(tests) }; - }, - client: false + var unitTestDir = grunt.config.get('unitTestDir'); + + // filter for non unit test related files + if (!~path.dirname(src).indexOf(unitTestDir)) return; + + var pattern = unitTestDir + '/specs/**/*.js'; + var appdir = grunt.config.get('app'); + + return { + tests: grunt.file.expand({}, pattern).map(function (filename) { + return path.relative(appdir, filename).replace(/\.js$/, ''); + }) + }; + } + } + }, + clientside: { + files: { + '<%= testUtilsDir %>/istanbul_reporter/report.jade.js': '<%= testUtilsDir %>/istanbul_reporter/report.clientside-jade' + }, + options: { + client: true, + amd: true, + namespace: false // return the template directly in the amd wrapper } } }; -}; - +}; \ No newline at end of file diff --git a/tasks/config/jshint.js b/tasks/config/jshint.js index 89bf0d1279f53..2a2bf9e68574d 100644 --- a/tasks/config/jshint.js +++ b/tasks/config/jshint.js @@ -3,7 +3,12 @@ module.exports = function (config) { // just lint the source dir source: { files: { - src: ['Gruntfile.js', '<%= src %>/**/*.js', '<%= unitTestDir %>/**/*.js', '<%= root %>/tasks/**/*.js'] + src: [ + 'Gruntfile.js', + '<%= src %>/**/*.js', + '<%= unitTestDir %>/**/*.js', + '<%= root %>/tasks/**/*.js' + ] } }, options: { diff --git a/tasks/config/less.js b/tasks/config/less.js index aa72cedd553d0..eeeb562c4cb70 100644 --- a/tasks/config/less.js +++ b/tasks/config/less.js @@ -1,8 +1,7 @@ module.exports = { src: { src: [ - '<%= app %>/styles/**/*.less', - '<%= app %>/apps/**/*.less', + '<%= app %>/**/styles/**/*.less', '!<%= src %>/**/_*.less' ], expand: true, diff --git a/tasks/config/mocha.js b/tasks/config/mocha.js index 13c593f409463..e8a519756c553 100644 --- a/tasks/config/mocha.js +++ b/tasks/config/mocha.js @@ -1,12 +1,14 @@ module.exports = { + options: { + log: true, + logErrors: true, + run: false + }, unit: { options: { - log: true, - logErrors: true, urls: [ - 'http://localhost:8001/' - ], - run: false + 'http://localhost:8000/test/unit/' + ] } } }; \ No newline at end of file diff --git a/tasks/config/watch.js b/tasks/config/watch.js index 2bfb964239acc..2335d76d64959 100644 --- a/tasks/config/watch.js +++ b/tasks/config/watch.js @@ -1,23 +1,29 @@ module.exports = function (grunt) { return { test: { - files: ['<%= unitTestDir %>/*.jade', '<%= unitTestDir %>/**/*.js'], - tasks: ['jade:test', 'mocha:unit'] + files: [ + '<%= unitTestDir %>/**/*.js' + ], + tasks: ['mocha:unit'] }, less: { files: [ - '<%= app %>/**/*.less', - '<%= src %>/courier/**/*.less' + '<%= app %>/**/styles/**/*.less', + '!<%= src %>/**/_*.less' ], tasks: ['less'] }, jade: { files: [ - '<%= app %>/**/*.jade', - '<%= src %>/courier/**/*.jade', - '!<%= unitTestDir %>/**/*.jade' + '<%= unitTestDir %>/index.jade' + ], + tasks: ['jade:test'] + }, + clientside_jade: { + files: [ + '<%= testUtilsDir %>/istanbul_reporter/report.clientside-jade' ], - tasks: ['jade'] + tasks: ['jade:clientside'] } }; }; diff --git a/tasks/dev.js b/tasks/dev.js index ee4c78feec73a..6afb009e02f04 100644 --- a/tasks/dev.js +++ b/tasks/dev.js @@ -1,3 +1,8 @@ module.exports = function (grunt) { - grunt.registerTask('dev', ['less', 'jade', 'connect:dev', 'watch']); + grunt.registerTask('dev', [ + 'less', + 'jade', + 'connect:dev', + 'watch' + ]); }; \ No newline at end of file diff --git a/tasks/server.js b/tasks/server.js index 17cdd157de2ab..00dc30c9c792a 100644 --- a/tasks/server.js +++ b/tasks/server.js @@ -1,4 +1,3 @@ module.exports = function (grunt) { grunt.registerTask('server', ['connect:dev:keepalive']); - grunt.registerTask('test_server', ['connect:test:keepalive']); }; \ No newline at end of file diff --git a/tasks/test.js b/tasks/test.js index cc19319f8f26b..02481b13babb0 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -2,13 +2,19 @@ module.exports = function (grunt) { /* jshint scripturl:true */ grunt.registerTask('test', [ 'jshint', - 'connect:test', - 'jade:test', + 'connect:dev', + 'jade', 'mocha:unit' ]); + grunt.registerTask('coverage', [ + 'blanket', + 'connect:dev', + 'mocha:coverage' + ]); + grunt.registerTask('test:watch', [ - 'connect:test', + 'connect:dev', 'watch:test' ]); }; diff --git a/tasks/utils/amd-wrapper.js b/tasks/utils/amd-wrapper.js new file mode 100644 index 0000000000000..56de3fbfb9a73 --- /dev/null +++ b/tasks/utils/amd-wrapper.js @@ -0,0 +1,26 @@ +module.exports = function amdWrapMiddleware(opts) { + opts = opts || {}; + + var root = opts.root || '/'; + var path = require('path'); + var fs = require('fs'); + var pathPrefix = opts.pathPrefix || '/amd-wrap/'; + + return function (req, res, next) { + // only allow prefixed requests + if (req.url.substring(0, pathPrefix.length) !== pathPrefix) return next(); + + // strip the prefix and form the filename + var filename = path.join(root, req._parsedUrl.pathname.replace('/amd-wrap/', '')); + + fs.readFile(filename, 'utf8', function (err, contents) { + // file does not exist + if (err) return next(err.code === 'ENOENT' ? void 0 : err); + + // respond with the wrapped code + res.statusCode = 200; + res.setHeader('Content-Type', 'application/javascript'); + res.end('define(function (require, exports, module) {\n' + contents + '\n});'); + }); + }; +}; \ No newline at end of file diff --git a/tasks/utils/instrumentation.js b/tasks/utils/instrumentation.js new file mode 100644 index 0000000000000..2c47a9a6445c5 --- /dev/null +++ b/tasks/utils/instrumentation.js @@ -0,0 +1,77 @@ +var Istanbul = require('istanbul'); +var i = new Istanbul.Instrumenter({ + embedSource: true, + preserveComments: true, + noAutoWrap: true +}); + +module.exports = function instrumentationMiddleware(opts) { + var fs = require('fs'); + var path = require('path'); + + // for root directory that files will be served from + var root = opts.root || '/'; + + // the root directory used to create a relative file path + // for display in coverage reports + var displayRoot = opts.displayRoot || null; + + // filter the files in root that can be instrumented + var filter = opts.filter || function (filename) { + // by default only instrument *.js files + return /\.js$/.test(filename); + }; + + // cache filename resolution + var fileMap = {}; + + function filenameForReq(req) { + if (!~req.url.indexOf('instrument=true')) return false; + + // expected absolute path to the file + var filename = path.join(root, req._parsedUrl.pathname); + + // shortcut for dev where we could be reloading on every save + if (fileMap[filename] !== void 0) return fileMap[filename]; + + var ret = filename; + + if (!fs.existsSync(filename) || !opts.filter(filename)) { + ret = false; + } + + // cache the return value for next time + fileMap[filename] = ret; + return ret; + } + + return function (req, res, next) { + // resolve the request to a readable filename + var filename = filenameForReq(req); + // the file either doesn't exist of it was filtered out by opts.filter + if (!filename) return next(); + + fs.readFile(filename, 'utf8', function (err, content) { + if (err) { + if (err.code !== 'ENOENT') { + // other issue, report! + return next(err); + } + + // file was deleted, clear cache and move on + delete fileMap[filename]; + return next(); + } + + res.statusCode = 200; + res.setHeader('Content-Type', 'application/javascript'); + res.end(i.instrumentSync( + content, + // make file names easier to read + displayRoot ? path.relative(displayRoot, filename) : filename + )); + }); + }; + + +}; \ No newline at end of file diff --git a/test/.jshintrc b/test/.jshintrc index 0cf87810d60c9..ea222a2c9872b 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -1,8 +1,6 @@ { "extends": "../src/.jshintrc", - "white": false, - "globals": { "module": false, "inject": false, diff --git a/test/unit/index.html b/test/unit/index.html index 8eb19b49f017e..7517defa9867d 100644 --- a/test/unit/index.html +++ b/test/unit/index.html @@ -1,21 +1,42 @@ -Kibana4 Tests
\ No newline at end of file +} + +function runTests() { + require(["../../test/unit/specs/apps/dashboard/index","../../test/unit/specs/apps/dashboard/mocks/modules","../../test/unit/specs/calculate_indices","../../test/unit/specs/courier","../../test/unit/specs/data_source","../../test/unit/specs/mapper"], function () { + window.mochaRunner = mocha.run().on('end', function () { + window.mochaResults = this.stats; + }); + }); +} + +if (COVERAGE) { + setupCoverage(runTests); +} else { + runTests(); +} \ No newline at end of file diff --git a/test/unit/index.jade b/test/unit/index.jade index cfdd8e2bc1045..90e7d705c5295 100644 --- a/test/unit/index.jade +++ b/test/unit/index.jade @@ -2,34 +2,53 @@ doctype html html head title Kibana4 Tests - link(rel="stylesheet", href="mocha.css") + link(rel="stylesheet", href='/node_modules/mocha/mocha.css') body #mocha - script(src="expect.js") - script(src="mocha.js") - script. - mocha.setup('bdd'); - // sauce labs & selenium inject global variables that break this - // mocha.checkLeaks(); - // mocha.globals(['mochaRunner', 'angular']); - script(src="bower_components/requirejs/require.js") - script(src="kibana/require.config.js") + script(src='/node_modules/expect.js/expect.js') + script(src='/node_modules/mocha/mocha.js') + script(src='/src/bower_components/requirejs/require.js') + script(src='/src/kibana/require.config.js') script(type="text/javascript"). + window.COVERAGE = !!(/coverage=true/i.test(location.search)); + mocha.setup('bdd'); + require.config({ - paths: { - sinon: '../sinon' - }, - shim: { - 'sinon/sinon': { - deps: [ - 'sinon/sinon-timers-1.8.2' - ], - exports: 'sinon' - } - } + baseUrl: '/src/kibana', + paths: { + sinon: '../../test/utils/sinon', + istanbul_reporter: '../../test/utils/istanbul_reporter' + }, + shim: { + 'sinon/sinon': { + deps: [ + 'sinon/sinon-timers-1.8.2' + ], + exports: 'sinon' + } + }, + // mark all requested files with instrument query param + urlArgs: COVERAGE ? 'instrument=true' : void 0 }); - require(!{tests}, function () { - window.mochaRunner = mocha.run().on('end', function () { - window.mochaResults = this.stats; + + function setupCoverage(done) { + document.title = document.title.replace('Tests', 'Coverage'); + require(['istanbul_reporter/reporter'], function (IstanbulReporter) { + mocha.reporter(IstanbulReporter); + done(); }); - }); + } + + function runTests() { + require(!{JSON.stringify(tests)}, function () { + window.mochaRunner = mocha.run().on('end', function () { + window.mochaResults = this.stats; + }); + }); + } + + if (COVERAGE) { + setupCoverage(runTests); + } else { + runTests(); + } diff --git a/test/unit/specs/apps/dashboard/index.js b/test/unit/specs/apps/dashboard/index.js index 75b5aeea4b117..9a6ed58665771 100644 --- a/test/unit/specs/apps/dashboard/index.js +++ b/test/unit/specs/apps/dashboard/index.js @@ -4,7 +4,7 @@ define(function (require) { var _ = require('lodash'); var $ = require('jquery'); var sinon = require('sinon/sinon'); - var configFile = require('../../../config.js'); + var configFile = require('configFile'); // Load the kibana app dependencies. require('angular-route'); @@ -19,13 +19,13 @@ define(function (require) { describe('Mapper', function () { var $scope; - beforeEach(function() { + beforeEach(function () { // Start the kibana module module('kibana'); // Create the scope - inject(function($rootScope, $controller) { + inject(function ($rootScope, $controller) { $scope = $rootScope.$new(); var dashCtrl = $controller('dashboard', { $scope: $scope diff --git a/test/unit/specs/apps/dashboard/mocks/modules.js b/test/unit/specs/apps/dashboard/mocks/modules.js index 8de8c1efc50e0..2d43954f6b43a 100644 --- a/test/unit/specs/apps/dashboard/mocks/modules.js +++ b/test/unit/specs/apps/dashboard/mocks/modules.js @@ -2,23 +2,23 @@ define(function (require) { var angular = require('angular'); - angular.module('app/dashboard',[]); + angular.module('app/dashboard', []); // Need this because the controller requires it - angular.module('kibana/directives',[]); + angular.module('kibana/directives', []); // create mock service for courier var mock = { getvalue: function () {} }; - angular.module('kibana/services',[]) - .service('courier',function () { + angular.module('kibana/services', []) + .service('courier', function () { return mock; }); // Could probably get rid of ngRoute if you want to stub it - angular.module('kibana',['ngRoute','kibana/services','app/dashboard']); + angular.module('kibana', ['ngRoute', 'kibana/services', 'app/dashboard']); }); \ No newline at end of file diff --git a/test/unit/specs/calculate_indices.js b/test/unit/specs/calculate_indices.js index 05db6f36584a7..745f9f8fb6e82 100644 --- a/test/unit/specs/calculate_indices.js +++ b/test/unit/specs/calculate_indices.js @@ -4,19 +4,19 @@ define(function (require) { describe('calculateIndices()', function () { - describe('error checking', function() { + describe('error checking', function () { it('should throw an error if start is > end', function () { expect(function () { calculateIndices(moment().add('day', 1), moment()); }).to.throwError(); }); it('should throw an error if interval is not [ hour, day, week, year ]', function () { - expect(function () { calculateIndices(moment().subtract('day', 1), moment(), 'century' ); }).to.throwError(); + expect(function () { calculateIndices(moment().subtract('day', 1), moment(), 'century'); }).to.throwError(); }); it('should throw an error if pattern is not set', function () { - expect(function () { calculateIndices(moment().subtract('day', 1), moment(), 'hour' ); }).to.throwError(); + expect(function () { calculateIndices(moment().subtract('day', 1), moment(), 'hour'); }).to.throwError(); }); }); - describe('hourly interval', function() { + describe('hourly interval', function () { beforeEach(function () { var date = '2014-01-15 04:30:10'; this.start = moment.utc(date).subtract('hours', 4); @@ -36,7 +36,7 @@ define(function (require) { }); }); - describe('daily interval', function() { + describe('daily interval', function () { beforeEach(function () { var date = '2014-01-15 04:30:10'; this.start = moment.utc(date).subtract('days', 4); @@ -56,7 +56,7 @@ define(function (require) { }); }); - describe('weekly interval', function() { + describe('weekly interval', function () { beforeEach(function () { var date = '2014-01-15 04:30:10'; this.start = moment.utc(date).subtract('week', 4); @@ -76,7 +76,7 @@ define(function (require) { }); }); - describe('yearly interval', function() { + describe('yearly interval', function () { beforeEach(function () { var date = '2014-01-15 04:30:10'; this.start = moment.utc(date).subtract('years', 4); diff --git a/test/unit/specs/courier.js b/test/unit/specs/courier.js index 4b755e3c2da41..c570afc0c7dfb 100644 --- a/test/unit/specs/courier.js +++ b/test/unit/specs/courier.js @@ -13,7 +13,18 @@ define(function (require) { expect(courier).to.be.a(Courier); }); - it('knows when a DataSource object has event listeners for the results event'); + it('knows when a DataSource object has event listeners for the results event', function () { + var courier = new Courier(); + var ds = courier.createSource('doc'); + + expect(courier._openSources('doc')).to.have.length(0); + ds.on('results', function () {}); + expect(courier._openSources('doc')).to.have.length(1); + ds.removeAllListeners('results'); + expect(courier._openSources('doc')).to.have.length(0); + }); + + it('executes queries on the interval for searches that have listeners for results'); describe('events', function () { diff --git a/test/unit/specs/mapper.js b/test/unit/specs/mapper.js index 108016a9654f0..7a2c1a0b1e389 100644 --- a/test/unit/specs/mapper.js +++ b/test/unit/specs/mapper.js @@ -1,11 +1,11 @@ define(function (require) { - var elasticsearch = require('../bower_components/elasticsearch/elasticsearch.js'); + var elasticsearch = require('bower_components/elasticsearch/elasticsearch'); var _ = require('lodash'); var sinon = require('sinon/sinon'); var Courier = require('courier/courier'); var DataSource = require('courier/data_source/data_source'); var Mapper = require('courier/mapper'); - var fieldMapping = require('../fixtures/field_mapping.js'); + var fieldMapping = require('../fixtures/field_mapping'); var client = new elasticsearch.Client({ host: 'localhost:9200', @@ -18,30 +18,32 @@ define(function (require) { describe('Mapper', function () { var server, source, mapper; - beforeEach(function() { + beforeEach(function () { source = courier.createSource('search') .index('valid') .size(5); mapper = new Mapper(courier); // Stub out a mini mapping response. - sinon.stub(client.indices, 'getFieldMapping',function (params, callback) { - if(params.index === 'valid') { - setTimeout(callback(undefined, fieldMapping),0); + sinon.stub(client.indices, 'getFieldMapping', function (params, callback) { + if (params.index === 'valid') { + setTimeout(callback(undefined, fieldMapping), 0); } else { - setTimeout(callback('Error: Not Found',undefined)); + setTimeout(callback('Error: Not Found', undefined)); } }); sinon.stub(client, 'getSource', function (params, callback) { - if(params.id === 'valid') { - setTimeout(callback(undefined,{'foo.bar': {'type': 'string'}}),0); + if (params.id === 'valid') { + setTimeout(callback(undefined, {'foo.bar': {'type': 'string'}}), 0); } else { - setTimeout(callback('Error: Not Found',undefined),0); + setTimeout(callback('Error: Not Found', undefined), 0); } }); - sinon.stub(client, 'delete', function (params, callback) {callback(undefined,true);}); + sinon.stub(client, 'delete', function (params, callback) { + callback(undefined, true); + }); }); afterEach(function () { @@ -57,7 +59,7 @@ define(function (require) { }); it('has getFieldsFromMapping function that returns a mapping', function (done) { - mapper.getFieldsFromMapping(source,function (err, mapping) { + mapper.getFieldsFromMapping(source, function (err, mapping) { expect(client.indices.getFieldMapping.called).to.be(true); expect(mapping['foo.bar'].type).to.be('string'); done(); @@ -69,7 +71,7 @@ define(function (require) { .index('invalid') .size(5); - mapper.getFieldsFromCache(source,function (err, mapping) { + mapper.getFieldsFromCache(source, function (err, mapping) { expect(client.getSource.called).to.be(true); expect(err).to.be('Error: Not Found'); done(); @@ -77,7 +79,7 @@ define(function (require) { }); it('has getFieldsFromCache that returns a mapping', function (done) { - mapper.getFieldsFromCache(source,function (err, mapping) { + mapper.getFieldsFromCache(source, function (err, mapping) { expect(client.getSource.called).to.be(true); expect(mapping['foo.bar'].type).to.be('string'); done(); diff --git a/test/utils/istanbul_reporter/report.clientside-jade b/test/utils/istanbul_reporter/report.clientside-jade new file mode 100644 index 0000000000000..3341b399bf1a5 --- /dev/null +++ b/test/utils/istanbul_reporter/report.clientside-jade @@ -0,0 +1,398 @@ +#coverage + h1#overview Coverage + #menu + li + a(href='#overview') + | overview + span.cov(class=coverageClass(cov.coverage)) #{cov.coverage | 0} + + for dir in cov.dirs + li.dirname= dir.name || '.' + for file in dir.files + li + span.cov(class=coverageClass(file.coverage)) #{file.coverage | 0} + a(href='##{file.filename}') + span.basename= file.relname + + a#logo(href='http://visionmedia.github.io/mocha/') m + + + #stats(class=coverageClass(cov.coverage)) + .percentage #{cov.coverage | 0}% + .sloc= cov.sloc + .hits= cov.hits + .misses= cov.misses + + #files + for dir in cov.dirs + for file in dir.files + .file + h2(id=file.filename)= file.filename + #stats(class=coverageClass(file.coverage)) + .percentage #{file.coverage | 0}% + .sloc= file.sloc + .hits= file.hits + .misses= file.misses + + table#source + thead + tr + th Line + th Hits + th Source + tbody + for line, number in file.source + if line.coverage > 0 + tr.hit + td.line= number + td.hits= line.coverage + td.source= line.source + else if 0 === line.coverage + tr.miss + td.line= number + td.hits 0 + td.source= line.source + else + tr + td.line= number + td.hits + td.source= line.source || ' ' + +style. + body { + font: 14px/1.6 "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 0; + color: #2C2C2C; + border-top: 2px solid #ddd; + } + + .coverage-stats { + font-size: 12px; + margin: 0; + padding: 0; + color: white; + text-align: right; + border: none; + box-shadow: none; + position: fixed; + bottom: 40px; + right: 10px; + } + + #coverage { + padding: 60px 60px 350px; + } + + h1 a { + color: inherit; + font-weight: inherit; + } + + h1 a:hover { + text-decoration: none; + } + + .onload h1 { + opacity: 1; + } + + h2 { + width: 80%; + margin-top: 80px; + margin-bottom: 0; + font-weight: 100; + letter-spacing: 1px; + border-bottom: 1px solid #eee; + } + + a { + color: #8A6343; + font-weight: bold; + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + + ul { + margin-top: 20px; + padding: 0 15px; + width: 100%; + } + + ul li { + float: left; + width: 40%; + margin-top: 5px; + margin-right: 60px; + list-style: none; + border-bottom: 1px solid #eee; + padding: 5px 0; + font-size: 12px; + } + + ul::after { + content: '.'; + height: 0; + display: block; + visibility: hidden; + clear: both; + } + + code { + font: 12px monaco, monospace; + } + + pre { + margin: 30px; + padding: 30px; + border: 1px solid #eee; + border-bottom-color: #ddd; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + -webkit-box-shadow: inset 0 0 10px #eee; + -moz-box-shadow: inset 0 0 10px #eee; + box-shadow: inset 0 0 10px #eee; + overflow-x: auto; + } + + img { + margin: 30px; + padding: 1px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: 0 3px 10px #dedede, 0 1px 5px #888; + -moz-box-shadow: 0 3px 10px #dedede, 0 1px 5px #888; + box-shadow: 0 3px 10px #dedede, 0 1px 5px #888; + max-width: 100%; + } + + footer { + background: #eee; + width: 100%; + padding: 50px 0; + text-align: right; + border-top: 1px solid #ddd; + } + + footer span { + display: block; + margin-right: 30px; + color: #888; + font-size: 12px; + } + + #menu { + position: fixed; + font-size: 12px; + overflow-y: auto; + top: 0; + right: 0; + margin: 0; + height: 100%; + padding: 15px 0; + text-align: right; + border-left: 1px solid #eee; + -moz-box-shadow: 0 0 2px #888 + , inset 5px 0 20px rgba(0,0,0,.5) + , inset 5px 0 3px rgba(0,0,0,.3); + -webkit-box-shadow: 0 0 2px #888 + , inset 5px 0 20px rgba(0,0,0,.5) + , inset 5px 0 3px rgba(0,0,0,.3); + box-shadow: 0 0 2px #888 + , inset 5px 0 20px rgba(0,0,0,.5) + , inset 5px 0 3px rgba(0,0,0,.3); + -webkit-font-smoothing: antialiased; + background: url("data:image/png;base64,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"); + } + + #menu::after { + display: block; + content: ''; + padding-top: 80px; + } + + #logo { + position: fixed; + bottom: 10px; + right: 10px; + background: rgba(255,255,255,.1); + font-size: 11px; + display: block; + width: 20px; + height: 20px; + line-height: 20px; + text-align: center; + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + border-radius: 20px; + -webkit-box-shadow: 0 0 3px rgba(0,0,0,.2); + -moz-box-shadow: 0 0 3px rgba(0,0,0,.2); + box-shadow: 0 0 3px rgba(0,0,0,.2); + color: inherit; + } + + #menu li a { + display: block; + color: white; + padding: 0 35px 0 25px; + -webkit-transition: background 300ms; + -moz-transition: background 300ms; + } + + #menu li { + position: relative; + list-style: none; + } + + #menu a:hover, + #menu a.active { + text-decoration: none; + background: rgba(255,255,255,.1); + } + + #menu li:hover .cov { + opacity: 1; + } + + #menu li.dirname { + color: white; + opacity: .60; + text-align: left; + padding: 15px 35px 0 20px; + } + + #menu li .dirname { + opacity: .60; + padding-right: 2px; + } + + #menu li .basename { + opacity: 1; + } + + #menu .cov { + background: rgba(0,0,0,.4); + position: absolute; + top: 0; + right: 8px; + font-size: 9px; + opacity: .6; + text-align: left; + width: 17px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + padding: 2px 3px; + text-align: center; + } + + #stats:nth-child(2n) { + display: inline-block; + margin-top: 15px; + border: 1px solid #eee; + padding: 10px; + -webkit-box-shadow: inset 0 0 2px #eee; + -moz-box-shadow: inset 0 0 2px #eee; + box-shadow: inset 0 0 2px #eee; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + } + + #stats div { + float: left; + padding: 0 5px; + } + + #stats::after { + display: block; + content: ''; + clear: both; + } + + #stats .sloc::after { + content: ' SLOC'; + color: #b6b6b6; + } + + #stats .percentage::after { + content: ' coverage'; + color: #b6b6b6; + } + + #stats .hits, + #stats .misses { + display: none; + } + + .high { + color: #00d4b4; + } + .medium { + color: #e87d0d; + } + .low { + color: #d4081a; + } + .terrible { + color: #d4081a; + font-weight: bold; + } + + table { + width: 80%; + margin-top: 10px; + border-collapse: collapse; + border: 1px solid #cbcbcb; + color: #363636; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + } + + table thead { + display: none; + } + + table td.line, + table td.hits { + width: 20px; + background: #eaeaea; + text-align: center; + font-size: 11px; + padding: 0 10px; + color: #949494; + } + + table td.hits { + width: 10px; + padding: 2px 5px; + color: rgba(0,0,0,.2); + background: #f0f0f0; + } + + tr.miss td.line, + tr.miss td.hits { + background: #e6c3c7; + } + + tr.miss td { + background: #f8d5d8; + } + + td.source { + padding-left: 15px; + line-height: 15px; + white-space: pre; + font: 12px monaco, monospace; + } + + code .comment { color: #ddd } + code .init { color: #2F6FAD } + code .string { color: #5890AD } + code .keyword { color: #8A6343 } + code .number { color: #2F6FAD } \ No newline at end of file diff --git a/test/utils/istanbul_reporter/report.jade.js b/test/utils/istanbul_reporter/report.jade.js new file mode 100644 index 0000000000000..ab79acca6be7e --- /dev/null +++ b/test/utils/istanbul_reporter/report.jade.js @@ -0,0 +1,322 @@ +define(['jade'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } + +return function template(locals) { +var buf = []; +var jade_mixins = {}; +var locals_ = (locals || {}),coverageClass = locals_.coverageClass,cov = locals_.cov; +buf.push("

Coverage

  • overview" + (jade.escape((jade.interp = cov.coverage | 0) == null ? '' : jade.interp)) + "
  • "); +// iterate cov.dirs +;(function(){ + var $$obj = cov.dirs; + if ('number' == typeof $$obj.length) { + + for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) { + var dir = $$obj[$index]; + +buf.push("
  • " + (jade.escape(null == (jade.interp = dir.name || '.') ? "" : jade.interp)) + "
  • "); +// iterate dir.files +;(function(){ + var $$obj = dir.files; + if ('number' == typeof $$obj.length) { + + for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) { + var file = $$obj[$index]; + +buf.push("
  • " + (jade.escape((jade.interp = file.coverage | 0) == null ? '' : jade.interp)) + "" + (jade.escape(null == (jade.interp = file.relname) ? "" : jade.interp)) + "
  • "); + } + + } else { + var $$l = 0; + for (var $index in $$obj) { + $$l++; var file = $$obj[$index]; + +buf.push("
  • " + (jade.escape((jade.interp = file.coverage | 0) == null ? '' : jade.interp)) + "" + (jade.escape(null == (jade.interp = file.relname) ? "" : jade.interp)) + "
  • "); + } + + } +}).call(this); + + } + + } else { + var $$l = 0; + for (var $index in $$obj) { + $$l++; var dir = $$obj[$index]; + +buf.push("
  • " + (jade.escape(null == (jade.interp = dir.name || '.') ? "" : jade.interp)) + "
  • "); +// iterate dir.files +;(function(){ + var $$obj = dir.files; + if ('number' == typeof $$obj.length) { + + for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) { + var file = $$obj[$index]; + +buf.push("
  • " + (jade.escape((jade.interp = file.coverage | 0) == null ? '' : jade.interp)) + "" + (jade.escape(null == (jade.interp = file.relname) ? "" : jade.interp)) + "
  • "); + } + + } else { + var $$l = 0; + for (var $index in $$obj) { + $$l++; var file = $$obj[$index]; + +buf.push("
  • " + (jade.escape((jade.interp = file.coverage | 0) == null ? '' : jade.interp)) + "" + (jade.escape(null == (jade.interp = file.relname) ? "" : jade.interp)) + "
  • "); + } + + } +}).call(this); + + } + + } +}).call(this); + +buf.push("m
    " + (jade.escape((jade.interp = cov.coverage | 0) == null ? '' : jade.interp)) + "%
    " + (jade.escape(null == (jade.interp = cov.sloc) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = cov.hits) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = cov.misses) ? "" : jade.interp)) + "
    "); +// iterate cov.dirs +;(function(){ + var $$obj = cov.dirs; + if ('number' == typeof $$obj.length) { + + for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) { + var dir = $$obj[$index]; + +// iterate dir.files +;(function(){ + var $$obj = dir.files; + if ('number' == typeof $$obj.length) { + + for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) { + var file = $$obj[$index]; + +buf.push("
    " + (jade.escape(null == (jade.interp = file.filename) ? "" : jade.interp)) + "
    " + (jade.escape((jade.interp = file.coverage | 0) == null ? '' : jade.interp)) + "%
    " + (jade.escape(null == (jade.interp = file.sloc) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = file.hits) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = file.misses) ? "" : jade.interp)) + "
    "); +// iterate file.source +;(function(){ + var $$obj = file.source; + if ('number' == typeof $$obj.length) { + + for (var number = 0, $$l = $$obj.length; number < $$l; number++) { + var line = $$obj[number]; + +if ( line.coverage > 0) +{ +buf.push(""); +} +else if ( 0 === line.coverage) +{ +buf.push(""); +} +else +{ +buf.push(""); +} + } + + } else { + var $$l = 0; + for (var number in $$obj) { + $$l++; var line = $$obj[number]; + +if ( line.coverage > 0) +{ +buf.push(""); +} +else if ( 0 === line.coverage) +{ +buf.push(""); +} +else +{ +buf.push(""); +} + } + + } +}).call(this); + +buf.push("
    LineHitsSource
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.coverage) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "0" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source || ' ') ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.coverage) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "0" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source || ' ') ? "" : jade.interp)) + "
    "); + } + + } else { + var $$l = 0; + for (var $index in $$obj) { + $$l++; var file = $$obj[$index]; + +buf.push("
    " + (jade.escape(null == (jade.interp = file.filename) ? "" : jade.interp)) + "
    " + (jade.escape((jade.interp = file.coverage | 0) == null ? '' : jade.interp)) + "%
    " + (jade.escape(null == (jade.interp = file.sloc) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = file.hits) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = file.misses) ? "" : jade.interp)) + "
    "); +// iterate file.source +;(function(){ + var $$obj = file.source; + if ('number' == typeof $$obj.length) { + + for (var number = 0, $$l = $$obj.length; number < $$l; number++) { + var line = $$obj[number]; + +if ( line.coverage > 0) +{ +buf.push(""); +} +else if ( 0 === line.coverage) +{ +buf.push(""); +} +else +{ +buf.push(""); +} + } + + } else { + var $$l = 0; + for (var number in $$obj) { + $$l++; var line = $$obj[number]; + +if ( line.coverage > 0) +{ +buf.push(""); +} +else if ( 0 === line.coverage) +{ +buf.push(""); +} +else +{ +buf.push(""); +} + } + + } +}).call(this); + +buf.push("
    LineHitsSource
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.coverage) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "0" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source || ' ') ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.coverage) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "0" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source || ' ') ? "" : jade.interp)) + "
    "); + } + + } +}).call(this); + + } + + } else { + var $$l = 0; + for (var $index in $$obj) { + $$l++; var dir = $$obj[$index]; + +// iterate dir.files +;(function(){ + var $$obj = dir.files; + if ('number' == typeof $$obj.length) { + + for (var $index = 0, $$l = $$obj.length; $index < $$l; $index++) { + var file = $$obj[$index]; + +buf.push("
    " + (jade.escape(null == (jade.interp = file.filename) ? "" : jade.interp)) + "
    " + (jade.escape((jade.interp = file.coverage | 0) == null ? '' : jade.interp)) + "%
    " + (jade.escape(null == (jade.interp = file.sloc) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = file.hits) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = file.misses) ? "" : jade.interp)) + "
    "); +// iterate file.source +;(function(){ + var $$obj = file.source; + if ('number' == typeof $$obj.length) { + + for (var number = 0, $$l = $$obj.length; number < $$l; number++) { + var line = $$obj[number]; + +if ( line.coverage > 0) +{ +buf.push(""); +} +else if ( 0 === line.coverage) +{ +buf.push(""); +} +else +{ +buf.push(""); +} + } + + } else { + var $$l = 0; + for (var number in $$obj) { + $$l++; var line = $$obj[number]; + +if ( line.coverage > 0) +{ +buf.push(""); +} +else if ( 0 === line.coverage) +{ +buf.push(""); +} +else +{ +buf.push(""); +} + } + + } +}).call(this); + +buf.push("
    LineHitsSource
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.coverage) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "0" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source || ' ') ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.coverage) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "0" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source || ' ') ? "" : jade.interp)) + "
    "); + } + + } else { + var $$l = 0; + for (var $index in $$obj) { + $$l++; var file = $$obj[$index]; + +buf.push("
    " + (jade.escape(null == (jade.interp = file.filename) ? "" : jade.interp)) + "
    " + (jade.escape((jade.interp = file.coverage | 0) == null ? '' : jade.interp)) + "%
    " + (jade.escape(null == (jade.interp = file.sloc) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = file.hits) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = file.misses) ? "" : jade.interp)) + "
    "); +// iterate file.source +;(function(){ + var $$obj = file.source; + if ('number' == typeof $$obj.length) { + + for (var number = 0, $$l = $$obj.length; number < $$l; number++) { + var line = $$obj[number]; + +if ( line.coverage > 0) +{ +buf.push(""); +} +else if ( 0 === line.coverage) +{ +buf.push(""); +} +else +{ +buf.push(""); +} + } + + } else { + var $$l = 0; + for (var number in $$obj) { + $$l++; var line = $$obj[number]; + +if ( line.coverage > 0) +{ +buf.push(""); +} +else if ( 0 === line.coverage) +{ +buf.push(""); +} +else +{ +buf.push(""); +} + } + + } +}).call(this); + +buf.push("
    LineHitsSource
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.coverage) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "0" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source || ' ') ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.coverage) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "0" + (jade.escape(null == (jade.interp = line.source) ? "" : jade.interp)) + "
    " + (jade.escape(null == (jade.interp = number) ? "" : jade.interp)) + "" + (jade.escape(null == (jade.interp = line.source || ' ') ? "" : jade.interp)) + "
    "); + } + + } +}).call(this); + + } + + } +}).call(this); + +buf.push("
    ");;return buf.join(""); +} + +}); \ No newline at end of file diff --git a/test/utils/istanbul_reporter/reporter.js b/test/utils/istanbul_reporter/reporter.js new file mode 100644 index 0000000000000..5a618b090bef0 --- /dev/null +++ b/test/utils/istanbul_reporter/reporter.js @@ -0,0 +1,239 @@ +/** + * TODO: move this into it's own module, with it's own dependencies + */ + +require.config({ + paths: { + // jade runtime is required by the AMD wrapped jade templates as "jade" + jade: '/amd-wrap/node_modules/grunt-contrib-jade/node_modules/jade/runtime' + } +}); + +// fake fs module to make jade/runtime.js happy +define('fs', function () {}); + +// fake process obejct to make browserify-path happy +window.process = window.process || { cwd: function () { return '.'; }}; + +// the actual reporter module +define(function (require) { + + var _ = require('lodash'); + var $ = require('jquery'); + + var InsertionText = require('/amd-wrap/node_modules/istanbul/lib/util/insertion-text.js'); + var objUtils = require('/amd-wrap/node_modules/istanbul/lib/object-utils.js'); + // var annotate = require('/amd-wrap/node_modules/istanbul/lib/annotate.js'); + var Progress = require('/amd-wrap/node_modules/mocha/lib/browser/progress.js'); + var path = require('/amd-wrap/node_modules/path-browserify/index.js'); + + var template = require('./report.jade'); + + var Base = window.Mocha.reporters.Base; + + function IstanbulReporter(runner) { + // "inherit" the base reporters characteristics + Base.call(this, runner); + + var stats = this.stats; + var gotoFile = window.location.hash; + if (gotoFile) window.location.hash = ''; + + $(document.body) + .html('
    '); + + var canvas = document.getElementsByTagName('canvas')[0]; + var ctx; + var progress; + if (canvas.getContext) { + var ratio = window.devicePixelRatio || 1; + canvas.style.width = canvas.width; + canvas.style.height = canvas.height; + canvas.width *= ratio; + canvas.height *= ratio; + ctx = canvas.getContext('2d'); + ctx.scale(ratio, ratio); + progress = new Progress(); + progress.size(40); + } + + runner.on('test end', function () { + if (progress) { + progress + .update((stats.tests / this.total) * 100 || 0) + .draw(ctx); + } + }); + + runner.on('end', function () { + var stats = _.omit(this.stats, 'start', 'end', 'suites'); + + stats['create report'] = Date.now(); + $(document.body).empty().append($(createReport())); // attempt to force parsing immediately + stats['create report'] = Date.now() - stats['create report']; + + toSec(stats, 'create report'); + toSec(stats, 'duration'); + + linkNav(); + show(stats); + if (gotoFile) { + var header = document.getElementById(gotoFile.substring(1)); + if (header) { + window.location.hash = gotoFile; + document.body.scrollTop = header.offsetTop; + } + } + }); + } + + function createReport() { + var summary = objUtils.summarizeCoverage(window.__coverage__); + + var dirs = _(window.__coverage__) + .map(convertFile) + .groupBy(function (file) { + var dir = path.dirname(file.filename); + return dir === '.' ? '' : dir; + }) + .transform(function (dirs, files, dirname) { + _.each(files, function (file) { + file.relname = dirname ? path.relative(dirname, file.filename) : file.filename; + }); + + dirs.push({ + name: dirname, + files: files, + coverage: _.reduce(files, function (sum, file) { + return sum + file.coverage; + }, 0) / files.length + }); + }, []) + .sortBy('name') + .value(); + + return template({ + cov: { + dirs: dirs, + coverage: summary.lines.pct, + sloc: summary.lines.total, + hits: summary.lines.covered, + misses: summary.lines.total - summary.lines.covered + }, + dirname: path.dirname, + relative: path.relative, + coverageClass: function (coverage) { + if (coverage >= 75) return 'high'; + if (coverage >= 50) return 'medium'; + if (coverage >= 25) return 'low'; + return 'terrible'; + } + }); + } + + function convertFile(file) { + var summary = objUtils.summarizeFileCoverage(file); + var count = 0; + var structured = file.code.map(function (str) { + count += 1; + return { + line: count, + covered: null, + text: new InsertionText(str, true) + }; + }); + var html = ''; + + structured.unshift({ + line: 0, + covered: null, + text: new InsertionText('') + }); + + _.forOwn(file.l, function (count, lineNumber) { + structured[lineNumber].covered = count > 0 ? true : false; + }); + + // annotate.Lines(file, structured); + //note: order is important, since statements typically result in spanning the whole line and doing branches late + //causes mismatched tags + // annotate.Branches(file, structured); + // annotate.Functions(file, structured); + // annotate.Statements(file, structured); + + structured.shift(); + + var context = { + filename: file.path, + sloc: summary.lines.total, + coverage: summary.lines.pct, + hits: summary.lines.covered, + misses: summary.lines.total - summary.lines.covered, + source: _.map(structured, function (line, lineNumber) { + return { + coverage: file.l[line.line], + source: line.text + '' + }; + }) + }; + + return context; + + // writer.write(detailTemplate(context)); + // writer.write('\n'); + // writer.write(footerTemplate(templateData)); + } + + function linkNav() { + var headings = $('h2').toArray(); + + $(window).scroll(function (e) { + var heading = find(window.scrollY); + if (!heading) return; + var links = document.querySelectorAll('#menu a') + , link; + + for (var i = 0, len = links.length; i < len; ++i) { + link = links[i]; + link.className = link.getAttribute('href') === '#' + heading.id + ? 'active' + : ''; + } + }); + + function find(y) { + var i = headings.length + , heading; + + while (i--) { + heading = headings[i]; + if (y >= heading.offsetTop) { + return heading; + } + } + } + } + + function toSec(stats, prop) { + return stats[prop] = (stats[prop] / 1000).toFixed(2) + ' sec'; + } + + function show(info) { + var width = _(info).keys().sortBy('length').pop().length; + + $('
    ')
    +      .addClass('coverage-stats')
    +      .appendTo('#menu')
    +      .text(
    +        _.map(info, function (val, name) {
    +          var row = val + ' - ' + name;
    +          if (width - name.length) {
    +            row += (new Array(width - name.length + 1)).join(' ');
    +          }
    +          return row;
    +        }).join('\n')
    +      );
    +  }
    +
    +  return IstanbulReporter;
    +});
    \ No newline at end of file