| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Code coverage using Istanbul for Ember apps and addons. Supports classic Ember CLI, Embroider, and Vite-based builds.
The following apply to classic ember-cli / Embroider builds only (not Vite):
Or with npm/pnpm:
npm install --save-dev ember-cli-code-coverage
# or
pnpm add -D ember-cli-code-coverageIn order to gather code coverage information, you must first install the Babel plugins in each project that you'd like to have instrumented.
For classic apps (ember-cli-build.js):
let app = new EmberApp(defaults, {
babel: {
plugins: [...require('ember-cli-code-coverage').buildBabelPlugin()],
},
});For embroider apps (ember-cli-build.js):
let app = new EmberApp(defaults, {
babel: {
plugins: [...require('ember-cli-code-coverage').buildBabelPlugin({ embroider: true })],
},
});For in-repo and standalone addons (index.js):
module.exports = {
name: require('./package').name,
options: {
babel: {
plugins: [...require('ember-cli-code-coverage').buildBabelPlugin()],
},
},
};For in-repo engines (index.js):
module.exports = EngineAddon.extend({
// ...
included() {
this._super.included.apply(this, arguments);
this.options.babel.plugins.push(...require('ember-cli-code-coverage').buildBabelPlugin());
},
});For app files in standalone addons (ember-cli-build.js):
let app = new EmberAddon(defaults, {
babel: {
plugins: [...require('ember-cli-code-coverage').buildBabelPlugin()]
},
});Add the following to your existing tests/test-helper.js:
import { forceModulesToBeLoaded, sendCoverage } from 'ember-cli-code-coverage/test-support';
import * as QUnit from 'qunit';
QUnit.done(async function() {
forceModulesToBeLoaded();
await sendCoverage();
});For v2 Embroider native addons based on https://github.com/embroider-build/addon-blueprint blueprint:
// babel.config.cjs
module.exports = {
plugins: [
['@babel/plugin-transform-typescript', { allExtensions: true, onlyRemoveTypeImports: true, allowDeclareFields: true }],
'@embroider/addon-dev/template-colocation-plugin',
['babel-plugin-ember-template-compilation', { targetFormat: 'hbs', transforms: [] }],
['module:decorator-transforms', { runtime: 'globals' }],
...require('ember-cli-code-coverage').buildBabelPlugin(),
],
};Coverage is collected by the test app test suite, so the app must set up tests/test-helper.js with sendCoverage() as shown in the Classic Ember CLI apps or Vite-based apps and addons sections.
For projects using @embroider/vite, add the coverage plugin to your Vite config.
This matches the ember-app-blueprint vite.config.mjs:
// vite.config.mjs
import { defineConfig } from 'vite';
import { extensions, classicEmberSupport, ember } from '@embroider/vite';
import { babel } from '@rollup/plugin-babel';
import { coveragePlugin } from 'ember-cli-code-coverage/vite';
const enableCoverage = process.env.COVERAGE === 'true';
export default defineConfig({
plugins: [
classicEmberSupport(),
ember(),
...(enableCoverage ? coveragePlugin() : []),
babel({
babelHelpers: 'runtime',
extensions,
}),
],
});No manual middleware wiring is needed — the addon auto-detects @embroider/vite and registers coverage middleware for you. See coverage collection under vite build + Testem for details and the manual override pattern.
This matches the ember-addon-blueprint vite.config.mjs:
// vite.config.mjs
import { defineConfig } from 'vite';
import { extensions, classicEmberSupport, ember } from '@embroider/vite';
import { babel } from '@rollup/plugin-babel';
import { coveragePlugin } from 'ember-cli-code-coverage/vite';
// For scenario testing
const isCompat = Boolean(process.env.ENABLE_COMPAT_BUILD);
const enableCoverage = process.env.COVERAGE === 'true';
export default defineConfig({
plugins: [
...(isCompat ? [classicEmberSupport()] : []),
ember(),
...(enableCoverage ? coveragePlugin() : []),
babel({
babelHelpers: 'inline',
extensions,
}),
],
build: {
rollupOptions: {
input: {
tests: 'tests/index.html',
},
},
},
});And the matching testem.cjs:
// testem.cjs
'use strict';
const { createViteTestemMiddleware } = require('ember-cli-code-coverage/testem');
if (typeof module !== 'undefined') {
module.exports = {
test_page: 'tests/index.html?hidepassed',
cwd: 'dist-tests',
disable_watching: true,
launch_in_ci: ['Chrome'],
launch_in_dev: ['Chrome'],
browser_args: {
Chrome: {
ci: [
process.env.CI ? '--no-sandbox' : null,
'--headless',
'--disable-dev-shm-usage',
'--mute-audio',
'--remote-debugging-port=0',
'--window-size=1440,900',
].filter(Boolean),
},
},
middleware: [createViteTestemMiddleware()],
};
}The coveragePlugin() function accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | process.env.COVERAGE === 'true' | Force-enable or force-disable coverage; falls back to checking coverageEnvVar. |
| coverageEnvVar | string | 'COVERAGE' | Environment variable that, when set to 'true', enables coverage. |
| exclude | string[] | ['**/node_modules/**', '**/tests/**', '**/mirage/**'] | Glob patterns to skip during instrumentation. |
| extensions | string[] | ['.js', '.ts', '.gjs', '.gts', '.mjs', '.mts'] | File extensions to instrument. |
| templateCoverage | boolean | false | Reserved for template branch coverage (requires the /glimmer module — not yet implemented). |
| coverage | object | {} | Overrides for report generation: { reporters, coverageFolder }. |
Example overriding only what you need. Note that exclude is a full replacement — repeat the defaults you want to keep:
coveragePlugin({
exclude: [
'**/node_modules/**',
'**/tests/**',
'**/mirage/**',
'**/vendor/**',
],
coverage: {
reporters: ['html', 'lcov', 'json-summary'],
coverageFolder: 'coverage',
},
});Instrumentation records hits in window.__coverage__, but reports are only written after the browser POSTs that payload to /write-coverage. Wire that up from ember-cli-code-coverage/test-support in tests/test-helper.js or tests/test-helper.ts (same pattern for either extension).
The following matches ember-app-blueprint tests/test-helper.ts, with coverage hooks added. Apps and addons both use this pattern.
// tests/test-helper.js — replace `my-app` with your package name / modulePrefix
import Application from 'my-app/app';
import config from 'my-app/config/environment';
import * as QUnit from 'qunit';
import { setApplication } from '@ember/test-helpers';
import { setup } from 'qunit-dom';
import { start as qunitStart, setupEmberOnerrorValidation } from 'ember-qunit';
import { forceModulesToBeLoaded, sendCoverage } from 'ember-cli-code-coverage/test-support';
export function start() {
setApplication(Application.create(config.APP));
setup(QUnit.assert);
setupEmberOnerrorValidation();
QUnit.done(async function () {
try {
forceModulesToBeLoaded();
} catch (_e) {
// Vite serves ESM; requirejs is not available — safe to ignore
}
await sendCoverage();
});
qunitStart();
}If you use vite build + Testem (output to dist-tests or dist), the Vite dev server's configureServer hook from coveragePlugin() only runs under vite dev, not during a production-style test build. For this case the addon automatically detects @embroider/vite in your project's package.json and registers createViteTestemMiddleware() for you via its testemMiddleware hook — no extra testem.cjs configuration is required.
If you need to customize the middleware (e.g. point at a different project root or change the report output folder), you can register it manually from testem.cjs:
// testem.cjs
'use strict';
const { createViteTestemMiddleware } = require('ember-cli-code-coverage/testem');
module.exports = {
test_page: 'tests/index.html?hidepassed',
cwd: 'dist-tests',
// ... other testem config
middleware: [
createViteTestemMiddleware({
root: process.cwd(), // Project root (default: process.cwd())
coverageFolder: 'coverage', // Output folder (default: 'coverage')
reporters: ['html', 'lcov', 'json-summary'], // Reporter list (default shown)
}),
],
};Run with coverage enabled:
COVERAGE=true npm run test
# or
COVERAGE=true pnpm testCoverage will only be generated when an environment variable is true (by default COVERAGE) and running your test command like normal.
For example:
COVERAGE=true ember test
If you want your coverage to work on both Unix and Windows, you can do this:
npm install cross-env --save-dev
and then:
cross-env COVERAGE=true ember test
When running with parallel set to true, the final reports can be merged by using ember coverage-merge. The final merged output will be stored in the coverageFolder.
If you intend to use ember test with the --path flag, you should generate the build with coverageEnvVar set as true. This is because the code is instrumented for coverage during the build.
For example:
COVERAGE=true ember build --environment=test --output-path=dist
followed by
COVERAGE=true ember test --path=dist
For Vite-based projects, TypeScript is handled natively by Vite/esbuild — no extra source-map configuration is required. The steps below apply only to classic ember-cli / Embroider builds.
Steps:
{
"compilerOptions": {
"inlineSourceMap": true,
"inlineSources": true
}
}const app = new EmberApp(defaults, {
babel: {
sourceMaps: 'inline',
},
sourcemaps: {
enabled: true,
extensions: ['js'],
},
});v3 ships hand-written TypeScript declarations for all public entry points (/babel, /vite, /istanbul, /testem, /test-support, /glimmer). See ARCHITECTURE.md for the full typed API.
const app = new EmberApp(defaults, {
'ember-template-imports': {
inline_source_map: true,
},
});Note: config/coverage.js is read by the classic Ember CLI / Embroider build pipeline only. The Vite plugin (ember-cli-code-coverage/vite) does not load config/coverage.js; configure it inline via coveragePlugin({ ... }) options. See the mapping table below and the Vite-based apps and addons section.
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/istanbuljs/istanbuljs)
coverageEnvVar: Defaults to COVERAGE. This is the environment variable that when set will cause coverage metrics to be generated.
reporters: Defaults to ['lcov', 'html']. The json-summary reporter will be added to anything set here, it is required. This can be any reporters supported by Istanbul. Reporters can be configured with array-style syntax, for example, here are options to lcov with a different projectRoot: [['lcov', { projectRoot: '/packages/addon' }], 'html']
excludes: Defaults to ['*/mirage/**/*']. An array of globs to exclude from instrumentation. Useful to exclude files from coverage statistics.
extension: Defaults to ['.gjs', '.gts', '.js', '.ts', '.cjs', '.mjs', '.mts', '.cts']. Tell Istanbul to instrument only files with the provided extensions.
coverageFolder: Defaults to coverage. A folder relative to the root of your project to store coverage results.
parallel: Defaults to false. Should be set to true if parallel testing is being used for separate test runs, for example when using ember-exam with the --partition flag. This will generate the coverage reports in directories suffixed with _<random_string> 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 in your package.json).
modifyAssetLocation: Optional function that will allow you to override where a file actually lives inside of your project. See Advanced customization on how to use this function in practice.
module.exports = {
coverageEnvVar: 'COV'
}buildBabelPlugin() accepts an optional object. Most users don't need to pass anything — the function reads config/coverage.js automatically. These options are only useful when you need to override the defaults programmatically (e.g. in ember-cli-build.js or babel.config.cjs):
| Option | Type | Default | Description |
|---|---|---|---|
| cwd | string | process.cwd() | Working directory for path resolution and config/coverage.js lookup. |
| embroider | boolean | false | Set to true for Embroider apps so the plugin resolves the rewritten-app working directory. |
| templateCoverage | boolean | false | When true, skips the GJS/GTS ignore plugin (reserved for future template coverage). |
Values from config/coverage.js (excludes, coverageEnvVar, extension) take precedence over the built-in defaults when the file exists. The function returns [] when the coverage env var is not 'true'.
For Vite-based projects, translate each config/coverage.js key to its coveragePlugin() equivalent:
| config/coverage.js | Vite equivalent (in vite.config.mjs) |
|---|---|
| coverageEnvVar | coveragePlugin({ coverageEnvVar: 'COV' }) |
| reporters | coveragePlugin({ coverage: { reporters: [...] } }) |
| coverageFolder | coveragePlugin({ coverage: { coverageFolder: '...' } }) |
| excludes (note: plural) | coveragePlugin({ exclude: [...] }) (note: singular) |
| extension | coveragePlugin({ extensions: [...] }) (note: plural) |
| parallel | not yet supported in the Vite pipeline |
| modifyAssetLocation | not applicable — Vite emits absolute paths and uses source maps |
To work, this addon has to post coverage results back to a middleware at /write-coverage.
If you are using ember-cli-mirage you should add the following:
// in mirage/config.js
this.passthrough('/write-coverage');
this.namespace = 'api'; // It's important that the passthrough for coverage is before the namespace, otherwise it will be prefixed.If you are using ember-cli-pretender you should add the following:
// where ever you set up the Pretender Server
var server = new Pretender(function () {
this.post('/write-coverage', this.passthrough);
});The forceModulesToBeLoaded function can potentially cause unintended side effects when executed. You can pass custom filter functions that allow you to specify which modules will be force loaded or not:
QUnit.done(async () => {
// type will be either webpack and/or require
forceModulesToBeLoaded((type, moduleName) => { return true; });
await sendCoverage();
});Under the hood, ember-cli-code-coverage attempts to "de-namespacify" paths into their real on disk location inside of project.root (ie give a namespaced path like lib/inrepo/components/foo.js would live in lib/inrepo/addon/components/foo.js). It makes some assumptions (where files live in in-repo addons vs app code for example) and sometimes those assumptions might not hold. Passing a function modifyAssetLocation in your configuration file will allow you to override where a file actually lives inside of your project. The returned string should be relative to your project root.
module.exports = {
modifyAssetLocation(root, relativePath) {
let appPath = relativePath.replace('my-project-name', 'app');
// here is an example of saying that `app/components/foo.js` actually
// lives in `lib/inrepo/app/components/foo.js` on disk.
if (fs.existsSync(path.join(root, 'lib', 'inrepo', appPath))) {
return path.join('lib', 'inrepo', appPath);
}
return false;
},
};The main entry point is fully backward compatible. If your setup looks like this, no changes are needed:
// ember-cli-build.js -- unchanged
const { buildBabelPlugin } = require('ember-cli-code-coverage');// test-helper.js -- unchanged
import { forceModulesToBeLoaded, sendCoverage } from 'ember-cli-code-coverage/test-support';v3 adds dedicated entry points for each concern. You can optionally use them for more explicit imports:
// Instead of:
const { buildBabelPlugin } = require('ember-cli-code-coverage');
// You can now also do:
const { buildBabelPlugin } = require('ember-cli-code-coverage/babel');If you are migrating to Vite (via @embroider/vite), replace the ember-cli Babel plugin setup with the Vite plugin:
// vite.config.mjs
import { coveragePlugin } from 'ember-cli-code-coverage/vite';
export default defineConfig({
plugins: [
ember(),
...(process.env.COVERAGE === 'true' ? coveragePlugin() : []),
babel({ extensions }), // see "Vite-based apps and addons" for babelHelpers
],
});Coverage collection happens automatically, via one of two paths depending on how you run tests:
In both cases you do not need to wire any middleware manually. See Vite-based apps and addons for the full config (including the project-specific babelHelpers value) and for the manual override pattern.
All modules ship hand-written TypeScript declarations. Import types directly:
import type { BabelPluginOptions } from 'ember-cli-code-coverage/babel';
import type { VitePluginOptions } from 'ember-cli-code-coverage/vite';
import type { CoverageConfig } from 'ember-cli-code-coverage/istanbul';This addon was inspired by ember-cli-blanket. The primary differences are that this addon uses Istanbul rather than Blanket for coverage and it instruments your application code as part of the build, when enabled.
| Back | FazBrowse Home | New Git URL |