diff --git a/README.md b/README.md index bfdb4a6e..e56fb5fa 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ Code coverage using [Istanbul](https://github.com/gotwarlost/istanbul) for Ember * If using Mocha, Testem `>= 1.6.0` for which you need ember-cli `> 2.4.3` * If using Mirage you need `ember-cli-mirage >= 0.1.13` * If using Pretender (even as a dependency of Mirage) you need `pretender >= 0.11.0` -* If using Mirage or Pretender, you need to [set up a passthrough for coverage to be written](#create-a-passthrough-when-intercepting-all-ajax-requests-in-tests). +* If using Mirage or Pretender, you need to [set up a passthrough for coverage to be written](#create-a-passthrough-when-intercepting-all-ajax-requests-in-tests). +* `ember-cli-babel >= 6.0.0` ## Installation @@ -33,7 +34,7 @@ When running with `parallel` set to true, the final reports can be merged by usi ## Configuration -Configuration is optional. It should be put in a file at `config/coverage.js` (`configPath` configuration in package.json is honored) +Configuration is optional. It should be put in a file at `config/coverage.js` (`configPath` configuration in package.json is honored). In addition to this you can configure Istanbul by adding a `.istanbul.yml` file to the root directory of your app (See https://github.com/gotwarlost/istanbul#configuring) #### Options @@ -45,10 +46,6 @@ Configuration is optional. It should be put in a file at `config/coverage.js` (` - `coverageFolder`: Defaults to `coverage`. A folder relative to the root of your project to store coverage results. -- `useBabelInstrumenter`: Defaults to `false`. Whether or not to use Babel instrumenter instead of default instrumenter. The Babel instrumenter is useful when you are using features of ESNext as it uses your Babel configuration defined in `ember-cli-build.js`. - -- `babelPlugins`: Defaults to `['babel-plugin-transform-async-to-generator']`. When using the Babel instrumenter, this specifies a set of additional plugins to pass to the parser. Use this to parse specific ESNext features you may be using in your app (decorators, for instance). - - `parallel`: Defaults to `false`. Should be set to true if parallel testing is being used, for example when using [ember-exam](https://github.com/trentmwillis/ember-exam) with the `--parallel` flag. This will generate the coverage reports in directories suffixed with `_` to avoid overwriting other threads reports. These reports can be joined by using the `ember coverage-merge` command (potentially as part of the [posttest hook](https://docs.npmjs.com/misc/scripts) in your `package.json`). #### Example @@ -58,7 +55,7 @@ Configuration is optional. It should be put in a file at `config/coverage.js` (` } ``` -## Create a passthrough when intercepting all ajax requests in tests +## Create a passthrough when intercepting all ajax requests in tests To work, this addon has to post coverage results back to a middleware at `/write-coverage`. diff --git a/addon/utils/my-covered-util.js b/addon/utils/my-covered-util.js new file mode 100644 index 00000000..50867b0d --- /dev/null +++ b/addon/utils/my-covered-util.js @@ -0,0 +1,3 @@ +export default function myCoveredUtil() { + return true; +} diff --git a/addon/utils/my-uncovered-util.js b/addon/utils/my-uncovered-util.js new file mode 100644 index 00000000..8f531cba --- /dev/null +++ b/addon/utils/my-uncovered-util.js @@ -0,0 +1,3 @@ +export default function myUncoveredUtil() { + return true; +} diff --git a/app/utils/my-covered-util.js b/app/utils/my-covered-util.js new file mode 100644 index 00000000..4c33e85b --- /dev/null +++ b/app/utils/my-covered-util.js @@ -0,0 +1 @@ +export { default } from 'ember-cli-code-coverage/utils/my-covered-util'; diff --git a/app/utils/my-uncovered-util.js b/app/utils/my-uncovered-util.js new file mode 100644 index 00000000..5ff69ce4 --- /dev/null +++ b/app/utils/my-uncovered-util.js @@ -0,0 +1,2 @@ +export { default } from 'ember-cli-code-coverage/utils/my-uncovered-util'; + diff --git a/index.js b/index.js index 5bfe4faf..de34bb3f 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ var existsSync = require('exists-sync'); var fs = require('fs-extra'); var attachMiddleware = require('./lib/attach-middleware'); var config = require('./lib/config'); +const walkSync = require('walk-sync'); const VersionChecker = require('ember-cli-version-checker'); function requireBabelPlugin(pluginName) { @@ -21,46 +22,34 @@ function requireBabelPlugin(pluginName) { return plugin; } +// Regular expression to extract the file extension from a path. +const EXT_RE = /\.[^\.]+$/; + module.exports = { name: 'ember-cli-code-coverage', - // Ember Methods - - _getParentOptions: function() { - let options; - - // The parent can either be an Addon or a Project. If it's the project, - // we want to use the app instead. - if (this.parent !== this.project) { - // the parent is an addon, so use its options directly - options = this.parent.options = this.parent.options || {}; - } else { - // the parent is the project, and therefore we need to use - // the app.options instead - options = this.app.options = this.app.options || {}; - } + /** + * Look up the file path from an ember module path. + * @type {Object} + */ + fileLookup: null, - return options; - }, + // Ember Methods included: function() { this._super.included.apply(this, arguments); - let parentOptions = this._getParentOptions(); + this.fileLookup = {}; if (!this._registeredWithBabel && this._isCoverageEnabled()) { let checker = new VersionChecker(this.parent).for('ember-cli-babel', 'npm'); if (checker.satisfies('>= 6.0.0')) { - const IstanbulPlugin = requireBabelPlugin('babel-plugin-istanbul'); + this.IstanbulPlugin = requireBabelPlugin('babel-plugin-istanbul'); - // Create babel options if they do not exist - parentOptions.babel = parentOptions.babel || {}; - - // Create and pull off babel plugins - let plugins = parentOptions.babel.plugins = parentOptions.babel.plugins || []; - - plugins.push([IstanbulPlugin, { exclude: this._getExcludes() }]); + this._instrumentAppDirectory(); + this._instrumentAddonDirectory(); + this._instrumentInRepoAddonDirectories(); } else { this.project.ui.writeWarnLine( 'ember-cli-code-coverage: You are using an unsupported ember-cli-babel version,' + @@ -75,7 +64,7 @@ module.exports = { contentFor: function(type) { if (type === 'test-body-footer' && this._isCoverageEnabled()) { var template = fs.readFileSync(path.join(__dirname, 'lib', 'templates', 'test-body-footer.html')).toString(); - return template.replace('{%PROJECT_NAME%}', this._parentName()); + return template.replace('{%ENTRIES%}', JSON.stringify(Object.keys(this.fileLookup).map(file => file.replace(EXT_RE, '')))); } return undefined; @@ -97,11 +86,78 @@ module.exports = { testemMiddleware: function(app) { if (!this._isCoverageEnabled()) { return; } - attachMiddleware(app, { configPath: this.project.configPath(), root: this.project.root }); + attachMiddleware(app, { + configPath: this.project.configPath(), + root: this.project.root, + fileLookup: this.fileLookup + }); + }, + + treeFor() { + // Only include test fixtures when testing the addon. + if (this._parentName() === this.name) { + return this._super.treeFor.apply(this, arguments); + } }, // Custom Methods + /** + * Instrument the "app" directory. + */ + _instrumentAppDirectory() { + const dir = path.join(this.project.root, 'app'); + let prefix = this.parent.isEmberCLIAddon() ? 'dummy' : this.parent.name(); + this._instrumentDirectory(this.app, dir, prefix); + }, + + /** + * Instrument the "addon" directory. + */ + _instrumentAddonDirectory() { + let addon = this._findCoveredAddon(); + if (addon) { + const dir = path.join(this.project.root, 'addon'); + this._instrumentDirectory(addon, dir, addon.name); + } + }, + + /** + * Instrument the in-repo-addon directories in "lib/*". + */ + _instrumentInRepoAddonDirectories() { + const pkg = this.project.pkg; + if (pkg['ember-addon'] && pkg['ember-addon'].paths) { + pkg['ember-addon'].paths.forEach((addonPath) => { + let addonName = path.basename(addonPath); + let addonDir = path.join(this.project.root, addonPath); + let addon = this.project.findAddonByName(addonName); + let addonAppDir = path.join(addonDir, 'app'); + let addonAddonDir = path.join(addonDir, 'addon'); + this._instrumentDirectory(this.app, addonAppDir, this.parent.name()); + this._instrumentDirectory(addon, addonAddonDir, addonName); + }); + } + }, + + /** + * Instrument directory helper. + * @param {Object} appOrAddon The Ember app or addon config. + * @param {String} dir The path to the Ember app or addon. + * @param {String} modulePrefix The prefix to the ember module ('app', 'dummy' or the name of the addon). + */ + _instrumentDirectory(appOrAddon, dir, modulePrefix) { + if (fs.existsSync(dir)) { + let options = appOrAddon.options = appOrAddon.options || {}; + options.babel = options.babel || {}; + let plugins = options.babel.plugins = options.babel.plugins || []; + plugins.push([this.IstanbulPlugin, { + exclude: this._getExcludes(), + include: this._getIncludes(dir, modulePrefix) + }]); + } + }, + /** * Thin wrapper around exists-sync that allows easy stubbing in tests * @param {String} path - path to check existence of @@ -119,14 +175,29 @@ module.exports = { return config(this.project.configPath()); }, + /** + * Get paths to include for coverage + * @param {String} dir Include all js files under this directory. + * @param {String} prefix The prefix to the ember module ('app', 'dummy' or the name of the addon). + * @returns {Array} include paths + */ + _getIncludes: function(dir, prefix) { + let dirname = path.relative(this.project.root, dir); + let globs = this.registry.extensionsForType('js').map((extension) => `**/*.${extension}`); + + return walkSync(dir, { directories: false, globs }).map(file => { + let module = prefix + '/' + file.replace(EXT_RE, '.js'); + this.fileLookup[module] = path.join(dirname, file); + return module; + }); + }, + /** * Get paths to exclude from coverage * @returns {Array} exclude paths */ _getExcludes: function() { - var excludes = this._getConfig().excludes || []; - - return excludes; + return this._getConfig().excludes || []; }, /** diff --git a/lib/attach-middleware.js b/lib/attach-middleware.js index 7159b331..6f1dab01 100644 --- a/lib/attach-middleware.js +++ b/lib/attach-middleware.js @@ -3,6 +3,7 @@ var bodyParser = require('body-parser'); var istanbul = require('istanbul-api'); var getConfig = require('./config'); +var path = require('path'); var crypto = require('crypto'); function logError(err, req, res, next) { @@ -10,10 +11,8 @@ function logError(err, req, res, next) { next(err); } -function fixFilePaths(coverageData) { - // TODO: munge `coverageData.path` to the "real" on disk paths - // so that tools like code-climate (and other coverage related things) - // work properly +function fixFilePaths(coverageData, fileLookup) { + coverageData.path = fileLookup[coverageData.path]; return coverageData; } @@ -35,12 +34,16 @@ module.exports = function(app, options) { } let reporter = istanbul.createReporter(); + if (config.coverageFolder) { + reporter.dir = path.join(options.root, config.coverageFolder); + } let map = istanbul.libCoverage.createCoverageMap(); let coverage = req.body; - Object.keys(coverage).forEach(filename => - map.addFileCoverage(fixFilePaths(coverage[filename])) - ); + Object.keys(options.fileLookup).forEach(filename => { + let fileCoverage = coverage[filename] || istanbul.libCoverage.createFileCoverage(filename).data; + map.addFileCoverage(fixFilePaths(fileCoverage, options.fileLookup)); + }); reporter.addAll(config.reporters); reporter.write(map); diff --git a/lib/config.js b/lib/config.js index 8704bb1c..d139aff0 100644 --- a/lib/config.js +++ b/lib/config.js @@ -42,7 +42,6 @@ function getDefaultConfig() { excludes: [ '*/mirage/**/*' ], - useBabelInstrumenter: false, reporters: [ 'html', 'lcov' diff --git a/lib/coverage-merge.js b/lib/coverage-merge.js index e79893e3..18186376 100644 --- a/lib/coverage-merge.js +++ b/lib/coverage-merge.js @@ -22,6 +22,7 @@ module.exports = { var coverageDirRegex = new RegExp(coverageFolder + '_.*'); let reporter = istanbul.createReporter(); + reporter.dir = path.join(coverageRoot, coverageFolder); let map = istanbul.libCoverage.createCoverageMap(); return new Promise(function (resolve, reject) { diff --git a/lib/templates/test-body-footer.html b/lib/templates/test-body-footer.html index fb8badc7..a5ab37b6 100644 --- a/lib/templates/test-body-footer.html +++ b/lib/templates/test-body-footer.html @@ -3,17 +3,7 @@ var REQUEST_ASYNC = !/PhantomJS/.test(window.navigator.userAgent); function sendCoverage(callback) { - Object.keys(require.entries).forEach(function(file) { - // Only load non-test files from the project - var parts = file.split('/'); - if (parts[0] === '{%PROJECT_NAME%}' && parts[1] !== 'tests') { - try { - require(file); - } catch(error) { - console.warn('Error occurred while evaluating `' + file + '`: ' + error.message + '\n' + error.stack); - } - } - }); + {%ENTRIES%}.forEach(require); var coverageData = window.__coverage__; var data = JSON.stringify(coverageData || {}); @@ -62,16 +52,16 @@ data = JSON.parse(data); } - if (!data || !data.total) { + if (!data) { return; } var results = ['Lines', 'Branches', 'Functions', 'Statements'] .filter(function (name) { - return data.total[name.toLowerCase()] + return name.toLowerCase(); }) .map(function (name) { - return name + ' ' + data.total[name.toLowerCase()].pct + '%' + return name + ' ' + data[name.toLowerCase()].pct + '%' }); var resultsText = document.createTextNode(results.join(' | ')); diff --git a/package.json b/package.json index 5910df4a..94bda93c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "ember-cli-app-version": "^1.0.0", "ember-cli-dependency-checker": "^1.3.0", "ember-cli-eslint": "^3.0.0", - "ember-cli-htmlbars": "^1.1.1", "ember-cli-htmlbars-inline-precompile": "^0.4.0", "ember-cli-inject-live-reload": "^1.4.1", "ember-cli-qunit": "^4.0.0", @@ -59,21 +58,15 @@ "babel-plugin-istanbul": "^4.1.5", "babel-plugin-transform-async-to-generator": "^6.24.1", "body-parser": "^1.15.0", - "broccoli-filter": "^1.2.3", - "broccoli-funnel": "^1.0.1", - "broccoli-merge-trees": "^1.1.1", "ember-cli-babel": "^6.8.2", + "ember-cli-htmlbars": "^1.1.1", "ember-cli-version-checker": "^2.0.0", - "escodegen": "^1.8.0", - "esprima": "^3.1.3", "exists-sync": "0.0.3", "extend": "^3.0.0", "fs-extra": "^0.26.7", "istanbul-api": "^1.1.14", "node-dir": "^0.1.16", - "rsvp": "^3.2.1", - "source-map": "0.5.6", - "string.prototype.startswith": "^0.2.0" + "rsvp": "^3.2.1" }, "ember-addon": { "configPath": "tests/dummy/config", diff --git a/test/integration/coverage-test.js b/test/integration/coverage-test.js index 72cb7a4f..63da1b92 100644 --- a/test/integration/coverage-test.js +++ b/test/integration/coverage-test.js @@ -31,7 +31,7 @@ describe('`ember test`', function() { expect(file('coverage/lcov-report/index.html')).to.not.be.empty; expect(file('coverage/index.html')).to.not.be.empty; var summary = fs.readJSONSync('coverage/coverage-summary.json'); - expect(summary.total.lines.pct).to.equal(100); + expect(summary.total.lines.pct).to.equal(50); }); }); @@ -43,29 +43,9 @@ describe('`ember test`', function() { }); }); - it('uses the babel instrumenter when the configuration is set', function() { + it('excludes files when the configuration is set', function() { this.timeout(100000); - fs.copySync('tests/dummy/config/coverage-babel.js', 'tests/dummy/config/coverage.js'); - return runCommand('ember', ['test'], {env: {COVERAGE: true}}).then(function() { - expect(file('coverage/lcov-report/index.html')).to.not.be.empty; - expect(file('coverage/index.html')).to.not.be.empty; - var summary = fs.readJSONSync('coverage/coverage-summary.json'); - expect(summary.total.lines.pct).to.equal(100); - }); - }); - - it('works with the babel instrumenter and ES2017 async functions', function() { - this.timeout(100000); - fs.copySync('tests/dummy/config/coverage-babel.js', 'tests/dummy/config/coverage.js'); - // We want something that will always be evaluated whenever the app starts. - fs.writeFileSync('tests/dummy/app/routes/index.js', ` -import Ember from 'ember'; -export default Ember.Route.extend({ - async model() { - return {}; - } -}); -`); + fs.copySync('tests/dummy/config/coverage-excludes.js', 'tests/dummy/config/coverage.js'); return runCommand('ember', ['test'], {env: {COVERAGE: true}}).then(function() { expect(file('coverage/lcov-report/index.html')).to.not.be.empty; expect(file('coverage/index.html')).to.not.be.empty; @@ -85,11 +65,7 @@ export default Ember.Route.extend({ expect(file('coverage/lcov-report/index.html')).to.not.be.empty; expect(file('coverage/index.html')).to.not.be.empty; var summary = fs.readJSONSync('coverage/coverage-summary.json'); - expect(summary.total.lines.pct).to.equal(100); - expect(summary['tests/dummy/app/resolver.js'].lines.pct).to.equal(100); - expect(summary['tests/dummy/app/app.js'].lines.pct).to.equal(100); - expect(summary['tests/dummy/app/router.js'].lines.pct).to.equal(100); - expect(summary['tests/dummy/app/templates/application.hbs'].lines.pct).to.equal(100); + expect(summary.total.lines.pct).to.equal(50); }); }); @@ -106,11 +82,7 @@ export default Ember.Route.extend({ expect(file(coverageFolder + '/lcov-report/index.html')).to.not.be.empty; expect(file(coverageFolder + '/index.html')).to.not.be.empty; var summary = fs.readJSONSync(coverageFolder + '/coverage-summary.json'); - expect(summary.total.lines.pct).to.equal(100); - expect(summary['tests/dummy/app/resolver.js'].lines.pct).to.equal(100); - expect(summary['tests/dummy/app/app.js'].lines.pct).to.equal(100); - expect(summary['tests/dummy/app/router.js'].lines.pct).to.equal(100); - expect(summary['tests/dummy/app/templates/application.hbs'].lines.pct).to.equal(100); + expect(summary.total.lines.pct).to.equal(50); }); }); }); diff --git a/test/unit/index-test.js b/test/unit/index-test.js index 4a5cbb20..41630093 100644 --- a/test/unit/index-test.js +++ b/test/unit/index-test.js @@ -3,6 +3,8 @@ var expect = require('chai').expect; var sinon = require('sinon'); var Index = require('../../index.js'); +var path = require('path'); + describe('index.js', function() { var sandbox; @@ -11,9 +13,11 @@ describe('index.js', function() { Index.registry = { extensionsForType: function() { - return ['hbs']; + return ['js']; } }; + Index.parent = Index.project = Index.app = Index.IstanbulPlugin = null; + sandbox.stub(Index, 'fileLookup', {}); }); afterEach(function() { @@ -34,12 +38,13 @@ describe('index.js', function() { describe('with coverage enabled', function() { beforeEach(function() { sandbox.stub(Index, '_isCoverageEnabled').returns(true); + Index.fileLookup = { + 'some/module.js': 'some/file.js', + 'some/other/module.js': 'some/other/file.js' + }; Index.parent = { isEmberCLIAddon: function() { return false; - }, - name: function() { - return 'fake-app'; } }; }); @@ -53,7 +58,7 @@ describe('index.js', function() { }); it('includes the project name in the template for test-body-footer', function() { - expect(Index.contentFor('test-body-footer')).to.match(/fake-app/); + expect(Index.contentFor('test-body-footer')).to.match(/`["some/module", "some/other/module"`]/); }); }); }); @@ -108,164 +113,30 @@ describe('index.js', function() { }); }); - describe('_doesFileExistInCurrentProjectApp', function() { - describe('when file exists', function() { - var result; - - beforeEach(function() { - sandbox.stub(Index, '_existsSync').returns(true); - result = Index._doesFileExistInCurrentProjectApp('adapters/application.js'); - }); - - it('uses path to file in app', function() { - expect(Index._existsSync.lastCall.args).to.eql(['app/adapters/application.js']); - }); - - it('returns true', function() { - expect(result).to.be.true; - }); - }); - - describe('when file does not exist', function() { - beforeEach(function() { - sandbox.stub(Index, '_existsSync').returns(false); - }); - - describe('when template file exists', function() { - var result; - - beforeEach(function() { - sandbox.stub(Index, '_doesTemplateFileExist').returns(true); - result = Index._doesFileExistInCurrentProjectApp('templates/application.js'); - }); - - it('uses path to file in app', function() { - expect(Index._existsSync.lastCall.args).to.eql(['app/templates/application.js']); - }); - - it('returns true', function() { - expect(result).to.be.true; - }); - }); - - describe('when template file does not exist', function() { - var result; - - beforeEach(function() { - sandbox.stub(Index, '_doesTemplateFileExist').returns(false); - result = Index._doesFileExistInCurrentProjectApp('templates/application.js'); - }); - - it('uses path to file in app', function() { - expect(Index._existsSync.lastCall.args).to.eql(['app/templates/application.js']); - }); - - it('returns false', function() { - expect(result).to.be.false; - }); - }); + describe('_getIncludes', function() { + beforeEach(function() { + sandbox.stub(Index, 'project', { root: process.cwd() }); }); - }); - - describe('_doesFileExistInDummyApp', function() { - describe('when file exists', function() { - var result; - - beforeEach(function() { - sandbox.stub(Index, '_existsSync').returns(true); - result = Index._doesFileExistInDummyApp('adapters/application.js'); - }); - it('uses path to file in dummy app', function() { - expect(Index._existsSync.lastCall.args).to.eql(['tests/dummy/app/adapters/application.js']); - }); - - it('returns true', function() { - expect(result).to.be.true; + it('gets files to include from the app directory', function() { + Index._getIncludes('app', 'my-app'); + expect(Index.fileLookup).to.deep.equal({ + 'my-app/utils/my-covered-util.js': 'app/utils/my-covered-util.js', + 'my-app/utils/my-uncovered-util.js': 'app/utils/my-uncovered-util.js' }); }); - describe('when file does not exist', function() { - beforeEach(function() { - sandbox.stub(Index, '_existsSync').returns(false); - }); - - describe('when template file exists', function() { - var result; - - beforeEach(function() { - sandbox.stub(Index, '_doesTemplateFileExist').returns(true); - result = Index._doesFileExistInDummyApp('templates/application.js'); - }); - - it('uses path to file in dummy app', function() { - expect(Index._existsSync.lastCall.args).to.eql(['tests/dummy/app/templates/application.js']); - }); - - it('returns true', function() { - expect(result).to.be.true; - }); - }); - - describe('when template file does not exist', function() { - var result; - - beforeEach(function() { - sandbox.stub(Index, '_doesTemplateFileExist').returns(false); - result = Index._doesFileExistInDummyApp('templates/application.js'); - }); - - it('uses path to file in dummy app', function() { - expect(Index._existsSync.lastCall.args).to.eql(['tests/dummy/app/templates/application.js']); - }); - - it('returns false', function() { - expect(result).to.be.false; - }); - }); - }); - }); - - describe('_doesTemplateFileExist', function() { - describe('when file exists', function() { - var result; - - beforeEach(function() { - sandbox.stub(Index, '_existsSync').returns(true); - result = Index._doesTemplateFileExist('app/templates/application.js'); - }); - - it('uses path to hbs file', function() { - expect(Index._existsSync.lastCall.args).to.eql(['app/templates/application.hbs']); - }); - - it('returns true', function() { - expect(result).to.be.true; - }); - }); - - describe('when file does not exist', function() { - var result; - - beforeEach(function() { - sandbox.stub(Index, '_existsSync').returns(false); - result = Index._doesTemplateFileExist('app/templates/application.js'); - }); - - it('uses path to hbs file', function() { - expect(Index._existsSync.lastCall.args).to.eql(['app/templates/application.hbs']); - }); - - it('returns false', function() { - expect(result).to.be.false; + it('gets files to include from the addon directory', function() { + Index._getIncludes('addon', 'my-addon'); + expect(Index.fileLookup).to.deep.equal({ + 'my-addon/utils/my-covered-util.js': 'addon/utils/my-covered-util.js', + 'my-addon/utils/my-uncovered-util.js': 'addon/utils/my-uncovered-util.js' }); }); }); describe('_getExcludes', function() { beforeEach(function() { - sandbox.stub(Index, '_filterOutAddonFiles').returns('test'); - Index.parent = { isEmberCLIAddon: function() { return false; @@ -284,17 +155,10 @@ describe('index.js', function() { results = Index._getExcludes(); }); - it('returns one exclude', function() { - expect(results.length).to.equal(1); - }); - - it('exclude is a function', function() { - expect(typeof results[0]).to.equal('function'); + it('returns no excludes', function() { + expect(results.length).to.equal(0); }); - it('exclude is expected function', function() { - expect(results[0]()).to.equal('test'); - }); }); describe('when excludes defined in config', function() { @@ -308,21 +172,14 @@ describe('index.js', function() { results = Index._getExcludes(); }); - it('returns two excludes', function() { - expect(results.length).to.equal(2); + it('returns one exclude', function() { + expect(results.length).to.equal(1); }); - it('first exclude is from config', function() { + it('exclude is from config', function() { expect(results[0]).to.eql('*/mirage/**/*'); }); - it('second exclude is a function', function() { - expect(typeof results[1]).to.equal('function'); - }); - - it('second exclude is expected function', function() { - expect(results[1]()).to.equal('test'); - }); }); }); @@ -472,4 +329,216 @@ describe('index.js', function() { expect(result.name).to.equal('my-addon'); }); }); + + describe('_instrumentDirectory', function() { + beforeEach(function() { + sandbox.stub(Index, 'IstanbulPlugin', 'istanbul'); + sandbox.stub(Index, '_getExcludes').returns([]); + sandbox.stub(Index, 'project', { root: process.cwd() }); + sandbox.stub(Index, 'parent', { + name() { return 'my-app' }, + }); + sandbox.stub(Index, 'app', {}); + }); + + describe('_instrumentAppDirectory', function() { + + describe('for an app', function() { + beforeEach(function() { + sandbox.stub(Index, 'parent', { + name() { return 'my-app' }, + isEmberCLIAddon() { return false } + }); + }); + + it('instruments the app directory', function() { + Index._instrumentAppDirectory(); + expect(Index.fileLookup).to.deep.equal({ + 'my-app/utils/my-covered-util.js': 'app/utils/my-covered-util.js', + 'my-app/utils/my-uncovered-util.js': 'app/utils/my-uncovered-util.js' + }); + expect(Index.app).to.deep.equal({ + options: { + babel: { + plugins: [ + [ + 'istanbul', + { + exclude: [], + include: [ + 'my-app/utils/my-covered-util.js', + 'my-app/utils/my-uncovered-util.js' + ] + } + ] + ] + } + } + }); + }); + }); + + describe('for an addon', function() { + beforeEach(function() { + sandbox.stub(Index, 'parent', { + name() { return 'my-app' }, + isEmberCLIAddon() { return true } + }); + }); + + it('instruments the app directory', function() { + Index._instrumentAppDirectory(); + expect(Index.fileLookup).to.deep.equal({ + 'dummy/utils/my-covered-util.js': 'app/utils/my-covered-util.js', + 'dummy/utils/my-uncovered-util.js': 'app/utils/my-uncovered-util.js' + }); + expect(Index.app).to.deep.equal({ + options: { + babel: { + plugins: [ + [ + 'istanbul', + { + exclude: [], + include: [ + 'dummy/utils/my-covered-util.js', + 'dummy/utils/my-uncovered-util.js' + ] + } + ] + ] + } + } + }); + }); + }); + + }); + + describe('_instrumentAddonDirectory', function() { + + describe('for an app', function() { + beforeEach(function() { + sandbox.stub(Index, '_findCoveredAddon').returns(null); + sandbox.spy(Index, '_instrumentDirectory'); + }); + + it('does not instrument the addon directory', function() { + Index._instrumentAddonDirectory(); + sinon.assert.notCalled(Index._instrumentDirectory); + }); + }); + + describe('for an addon', function() { + let addon = { + name: 'my-addon' + }; + + beforeEach(function() { + sandbox.stub(Index, '_findCoveredAddon').returns(addon); + }); + + afterEach(function() { + addon = null; + }); + + it('instruments the addon directory', function() { + Index._instrumentAddonDirectory(); + expect(Index.fileLookup).to.deep.equal({ + 'my-addon/utils/my-covered-util.js': 'addon/utils/my-covered-util.js', + 'my-addon/utils/my-uncovered-util.js': 'addon/utils/my-uncovered-util.js' + }); + expect(addon).to.deep.equal({ + name: 'my-addon', + options: { + babel: { + plugins: [ + [ + 'istanbul', + { + exclude: [], + include: [ + 'my-addon/utils/my-covered-util.js', + 'my-addon/utils/my-uncovered-util.js' + ] + } + ] + ] + } + } + }); + }); + }); + + }); + + describe('_instrumentInRepoAddonDirectories', function() { + + describe('for an app with no inrepo addons', function() { + beforeEach(function() { + sandbox.stub(Index, 'project', { pkg: { } }); + sandbox.spy(Index, '_instrumentDirectory'); + }); + + it('does not instrument any inrepo addon directories', function() { + Index._instrumentInRepoAddonDirectories(); + sinon.assert.notCalled(Index._instrumentDirectory); + }); + }); + + describe('for an app with an inrepo addon', function() { + let addon = {}; + + beforeEach(function() { + sandbox.stub(path, 'basename').returns('my-inrepo-addon'); + sandbox.stub(Index, 'project', { + pkg: { + 'ember-addon': { + paths: [ + '' + ] + } + }, + root: process.cwd(), + findAddonByName() { return addon; } + }); + }); + + afterEach(function() { + addon = null; + }); + + it('instruments the inrepo addon', function() { + Index._instrumentInRepoAddonDirectories(); + expect(Index.fileLookup).to.deep.equal({ + 'my-app/utils/my-covered-util.js': 'app/utils/my-covered-util.js', + 'my-app/utils/my-uncovered-util.js': 'app/utils/my-uncovered-util.js', + 'my-inrepo-addon/utils/my-covered-util.js': 'addon/utils/my-covered-util.js', + 'my-inrepo-addon/utils/my-uncovered-util.js': 'addon/utils/my-uncovered-util.js', + }); + expect(addon).to.deep.equal({ + options: { + babel: { + plugins: [ + [ + 'istanbul', + { + exclude: [], + include: [ + 'my-inrepo-addon/utils/my-covered-util.js', + 'my-inrepo-addon/utils/my-uncovered-util.js' + ] + } + ] + ] + } + } + }); + }); + }); + + }); + + }); + }); diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js index 831ad610..f796e79d 100644 --- a/tests/dummy/app/app.js +++ b/tests/dummy/app/app.js @@ -5,8 +5,6 @@ import config from './config/environment'; let App; -Ember.MODEL_FACTORY_INJECTIONS = true; - App = Ember.Application.extend({ modulePrefix: config.modulePrefix, podModulePrefix: config.podModulePrefix, diff --git a/tests/dummy/config/coverage-babel.js b/tests/dummy/config/coverage-babel.js deleted file mode 100644 index 9b29422a..00000000 --- a/tests/dummy/config/coverage-babel.js +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-env node */ - -module.exports = { - useBabelInstrumenter: true -}; diff --git a/tests/dummy/config/coverage-excludes.js b/tests/dummy/config/coverage-excludes.js new file mode 100644 index 00000000..e84a75d7 --- /dev/null +++ b/tests/dummy/config/coverage-excludes.js @@ -0,0 +1,7 @@ +/* eslint-env node */ + +module.exports = { + excludes: [ + '**/utils/my-uncovered-util.js' + ] +}; diff --git a/tests/unit/utils/my-covered-util-test.js b/tests/unit/utils/my-covered-util-test.js new file mode 100644 index 00000000..ba25a3ae --- /dev/null +++ b/tests/unit/utils/my-covered-util-test.js @@ -0,0 +1,10 @@ +import myCoveredUtil from 'dummy/utils/my-covered-util'; +import { module, test } from 'qunit'; + +module('Unit | Utility | my covered util'); + +// Replace this with your real tests. +test('it works', function(assert) { + let result = myCoveredUtil(); + assert.ok(result); +}); diff --git a/yarn.lock b/yarn.lock index 36e771c4..671bb9ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1466,7 +1466,7 @@ broccoli-lint-eslint@^3.3.0: lodash.defaultsdeep "^4.6.0" md5-hex "^2.0.0" -broccoli-merge-trees@^1.0.0, broccoli-merge-trees@^1.1.1, broccoli-merge-trees@^1.1.4: +broccoli-merge-trees@^1.0.0, broccoli-merge-trees@^1.1.4: version "1.2.4" resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-1.2.4.tgz#a001519bb5067f06589d91afa2942445a2d0fdb5" dependencies: @@ -2867,17 +2867,6 @@ escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^ version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -escodegen@^1.8.0: - version "1.8.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" - escope@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" @@ -2980,11 +2969,11 @@ esprima-fb@~15001.1001.0-dev-harmony-fb: version "15001.1001.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659" -esprima@^2.6.0, esprima@^2.7.1: +esprima@^2.6.0: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" -esprima@^3.1.1, esprima@^3.1.3, esprima@~3.1.0: +esprima@^3.1.1, esprima@~3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -3005,10 +2994,6 @@ esrecurse@^4.1.0: estraverse "~4.1.0" object-assign "^4.0.1" -estraverse@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" - estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -4955,12 +4940,6 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" -node-dir@^0.1.16: - version "0.1.16" - resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.16.tgz#d2ef583aa50b90d93db8cdd26fcea58353957fe4" - dependencies: - minimatch "^3.0.2" - node-fetch@^1.3.3: version "1.6.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" @@ -6260,7 +6239,7 @@ source-map@0.4.x, source-map@^0.4.2, source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@0.5.6, source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1: +source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" @@ -6268,12 +6247,6 @@ source-map@^0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" -source-map@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" - dependencies: - amdefine ">=0.0.4" - spawn-args@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/spawn-args/-/spawn-args-0.2.0.tgz#fb7d0bd1d70fd4316bd9e3dec389e65f9d6361bb" @@ -6353,10 +6326,6 @@ string-width@^2.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^3.0.0" -string.prototype.startswith@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/string.prototype.startswith/-/string.prototype.startswith-0.2.0.tgz#da68982e353a4e9ac4a43b450a2045d1c445ae7b" - string_decoder@0.10, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"