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 @@ -