├── test
├── mocha.opts
├── fixtures
│ └── inputs
│ │ ├── src
│ │ └── example.ts
│ │ └── test
│ │ └── test.spec.ts
├── tsconfig.json
├── karma.conf.js
└── index.spec.js
├── examples
└── webpack
│ ├── .gitignore
│ ├── src
│ └── example.ts
│ ├── tsconfig.json
│ ├── test
│ └── test.spec.ts
│ ├── package.json
│ └── karma.conf.js
├── .gitignore
├── .travis.yml
├── LICENSE
├── package.json
├── README.md
└── index.js
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --timeout 5000
--------------------------------------------------------------------------------
/examples/webpack/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .nyc_output
3 | coverage
4 | test/fixtures/outputs
--------------------------------------------------------------------------------
/examples/webpack/src/example.ts:
--------------------------------------------------------------------------------
1 | export class Foo {
2 |
3 | bar(): boolean {
4 | return true; //covered
5 | }
6 |
7 | baz(): boolean {
8 | return false; // not covered
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - '7'
5 | - '6'
6 | - '4'
7 |
8 | script: npm test
9 |
10 | notifications:
11 | email: false
12 |
13 | cache:
14 | directories:
15 | - node_modules
--------------------------------------------------------------------------------
/test/fixtures/inputs/src/example.ts:
--------------------------------------------------------------------------------
1 | export class Foo {
2 |
3 | bar(): boolean {
4 | return true; //covered
5 | }
6 |
7 | baz(): boolean {
8 | return false; // not covered
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "removeComments": false,
8 | "noImplicitAny": false
9 | },
10 | "compileOnSave": false
11 | }
--------------------------------------------------------------------------------
/test/fixtures/inputs/test/test.spec.ts:
--------------------------------------------------------------------------------
1 | import {expect} from 'chai';
2 | import {Foo} from './../src/example';
3 |
4 | describe('Foo', () => {
5 |
6 | it('should return true', () => {
7 | const foo: Foo = new Foo();
8 | expect(foo.bar()).to.be.true;
9 | });
10 |
11 | });
--------------------------------------------------------------------------------
/examples/webpack/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "removeComments": false,
8 | "noImplicitAny": false
9 | },
10 | "compileOnSave": false
11 | }
--------------------------------------------------------------------------------
/examples/webpack/test/test.spec.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import {expect} from 'chai';
4 | import {Foo} from './../src/example';
5 |
6 | describe('Foo', () => {
7 |
8 | it('should return true', () => {
9 | const foo: Foo = new Foo();
10 | expect(foo.bar()).to.be.true;
11 | });
12 |
13 | });
--------------------------------------------------------------------------------
/examples/webpack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "karma-remap-istanbul-example",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "Karma remap istanbul example",
6 | "scripts": {
7 | "test": "karma start"
8 | },
9 | "license": "MIT",
10 | "devDependencies": {
11 | "@types/chai": "^3.4.32",
12 | "@types/mocha": "^2.2.31",
13 | "chai": "^3.5.0",
14 | "karma": "^1.3.0",
15 | "karma-mocha": "^1.3.0",
16 | "karma-phantomjs-launcher": "^1.0.2",
17 | "karma-remap-istanbul": "^0.5.0",
18 | "karma-sourcemap-loader": "^0.3.7",
19 | "karma-webpack": "^2.0.2",
20 | "mocha": "^3.2.0",
21 | "sourcemap-istanbul-instrumenter-loader": "^0.2.0",
22 | "ts-loader": "^2.0.0",
23 | "typescript": "^2.1.4",
24 | "webpack": "^2.2.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 marc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | const webpackConfig = {
5 | module: {
6 | rules: [{
7 | test: /\.ts$/,
8 | loader: 'ts-loader?silent=true',
9 | exclude: /node_modules/
10 | }, {
11 | test: /src\/.+\.ts$/,
12 | exclude: /(node_modules|\.spec\.ts$)/,
13 | loader: 'sourcemap-istanbul-instrumenter-loader?force-sourcemap=true',
14 | enforce: 'post'
15 | }]
16 | },
17 | plugins: [
18 | new webpack.SourceMapDevToolPlugin({
19 | filename: null,
20 | test: /\.(ts|js)($|\?)/i
21 | })
22 | ],
23 | resolve: {
24 | extensions: ['.ts', '.js']
25 | }
26 | };
27 |
28 | module.exports = function (config) {
29 | config.set({
30 |
31 | basePath: './',
32 |
33 | browsers: ['PhantomJS'],
34 |
35 | frameworks: ['mocha'],
36 |
37 | singleRun: true,
38 |
39 | reporters: ['karma-remap-istanbul'],
40 |
41 | files: [
42 | 'fixtures/inputs/test/test.spec.ts'
43 | ],
44 |
45 | preprocessors: {
46 | 'fixtures/inputs/test/test.spec.ts': ['webpack', 'sourcemap']
47 | },
48 |
49 | webpack: webpackConfig,
50 |
51 | webpackMiddleware: {
52 | stats: 'errors-only',
53 | noInfo: true
54 | },
55 |
56 | remapIstanbulReporter: {
57 | reports: {
58 | 'json-summary': path.join(__dirname, '/fixtures/outputs/coverage.json')
59 | }
60 | },
61 |
62 | logLevel: config.LOG_DISABLE
63 |
64 | });
65 | };
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "karma-remap-istanbul",
3 | "version": "0.6.0",
4 | "description": "Call remap-istanbul as a karma reporter, enabling remapped reports on watch",
5 | "main": "index.js",
6 | "files": [
7 | "index.js"
8 | ],
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/marcules/karma-remap-istanbul.git"
12 | },
13 | "keywords": [
14 | "remap-istanbul",
15 | "karma-plugin",
16 | "karma-reporter"
17 | ],
18 | "author": "Marc A. Harnos ",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/marcules/karma-remap-istanbul/issues"
22 | },
23 | "homepage": "https://github.com/marcules/karma-remap-istanbul#readme",
24 | "dependencies": {
25 | "istanbul": "^0.4.3",
26 | "remap-istanbul": "^0.9.0"
27 | },
28 | "peerDependencies": {
29 | "karma": ">=0.9"
30 | },
31 | "scripts": {
32 | "lint": "xo",
33 | "pretest": "npm run lint",
34 | "test": "nyc mocha",
35 | "test:watch": "mocha --watch",
36 | "preversion": "npm test"
37 | },
38 | "devDependencies": {
39 | "@types/chai": "^3.4.34",
40 | "@types/mocha": "^2.2.33",
41 | "chai": "^3.5.0",
42 | "karma": "^1.3.0",
43 | "karma-mocha": "^1.3.0",
44 | "karma-phantomjs-launcher": "^1.0.2",
45 | "karma-sourcemap-loader": "^0.3.7",
46 | "karma-webpack": "^2.0.2",
47 | "mocha": "^3.2.0",
48 | "nyc": "^10.0.0",
49 | "rimraf": "^2.5.4",
50 | "sourcemap-istanbul-instrumenter-loader": "^0.2.0",
51 | "ts-loader": "^2.0.0",
52 | "typescript": "^2.1.0",
53 | "webpack": "^2.2.1",
54 | "xo": "^0.17.1"
55 | },
56 | "nyc": {
57 | "reporter": [
58 | "html",
59 | "text-summary"
60 | ]
61 | },
62 | "xo": {
63 | "space": true,
64 | "envs": [
65 | "node",
66 | "mocha"
67 | ]
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # karma-remap-istanbul
2 | Call remap-istanbul as a karma reporter, enabling remapped reports on watch
3 |
4 | ## Installation
5 |
6 | Install `karma-remap-istanbul` as a dev-dependency in your project.
7 |
8 | ```bash
9 | npm install karma-remap-istanbul --save-dev
10 | ```
11 |
12 | ## Configuration
13 |
14 | Add the plugin, reporter and reporter configuration in your `karma.conf.js`.
15 |
16 | ```js
17 | {
18 | plugins: ['karma-remap-istanbul'],
19 | reporters: ['progress', 'karma-remap-istanbul'],
20 | remapIstanbulReporter: {
21 | remapOptions: {}, //additional remap options
22 | reportOptions: {}, //additional report options
23 | reports: {
24 | lcovonly: 'path/to/output/coverage/lcov.info',
25 | html: 'path/to/output/html/report'
26 | }
27 | }
28 | }
29 | ```
30 |
31 | ### Example configuration with `karma-coverage`
32 | ```js
33 | {
34 | preprocessors: {
35 | 'build/**/!(*spec).js': ['coverage']
36 | },
37 | plugins: ['karma-remap-istanbul', 'karma-coverage'],
38 | reporters: ['progress', 'coverage', 'karma-remap-istanbul'],
39 | remapIstanbulReporter: {
40 | reports: {
41 | html: 'coverage'
42 | }
43 | }
44 | }
45 | ```
46 |
47 | You will need to either install `karma-coverage` and configure it as a preprocessor for your transpiled modules under test or instrument the modules under test as part of your build process (i.e. via a tool like webpack and the `sourcemap-istanbul-instrumenter-loader`). If the latter option is chosen, the coverage statistics will need to be stored by the build tool on the `__coverage__` global variable (istanbul's default) or karma will not transmit the coverage back to the runner. For a full e2e example please [look here](https://github.com/marcules/karma-remap-istanbul/tree/master/examples/webpack).
48 |
49 | ## Alternatives
50 | For some build tools there are better suited solutions than using this module.
51 | * babel users can use the [babel plugin](https://github.com/istanbuljs/babel-plugin-istanbul) as it doesn't rely on sourcemapping and so is less error prone and more accurate
52 | * system.js users can use [systemjs-istanbul](https://github.com/guybedford/systemjs-istanbul) as it has remap-istanbul support included
53 | * browserify users can use [karma-typescript](https://github.com/monounity/karma-typescript) as it has remap-istanbul support included
54 | * webpack users can use the [istanbul-instrumenter-loader](https://github.com/deepsweet/istanbul-instrumenter-loader) and the [karma-coverage-instanbul-reporter](https://github.com/mattlewis92/karma-coverage-istanbul-reporter)
55 |
--------------------------------------------------------------------------------
/examples/webpack/karma.conf.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack'); // eslint-disable-line import/no-unresolved
2 |
3 | const webpackConfig = {
4 | module: {
5 | rules: [{
6 | test: /\.ts$/,
7 | loader: 'ts-loader',
8 | exclude: /node_modules/
9 | }, {
10 | test: /src\/.+\.ts$/,
11 | exclude: /(node_modules|\.spec\.ts$)/,
12 | loader: 'sourcemap-istanbul-instrumenter-loader?force-sourcemap=true',
13 | enforce: 'post'
14 | }]
15 | },
16 | plugins: [
17 | new webpack.SourceMapDevToolPlugin({
18 | filename: null,
19 | test: /\.(ts|js)($|\?)/i
20 | })
21 | ],
22 | resolve: {
23 | extensions: ['.ts', '.js']
24 | }
25 | };
26 |
27 | module.exports = config => {
28 | config.set({
29 |
30 | // base path that will be used to resolve all patterns (eg. files, exclude)
31 | basePath: './',
32 |
33 | // frameworks to use
34 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
35 | frameworks: ['mocha'],
36 |
37 | // list of files / patterns to load in the browser
38 | files: [
39 | 'test/test.spec.ts'
40 | ],
41 |
42 | // preprocess matching files before serving them to the browser
43 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
44 | preprocessors: {
45 | 'test/test.spec.ts': ['webpack', 'sourcemap']
46 | },
47 |
48 | webpack: webpackConfig,
49 |
50 | remapIstanbulReporter: {
51 | reports: {
52 | html: 'coverage/html',
53 | 'text-summary': null
54 | }
55 | },
56 |
57 | phantomjsLauncher: {
58 | // Have phantomjs exit if a ResourceError is encountered (useful if karma exits without killing phantom)
59 | exitOnResourceError: true
60 | },
61 |
62 | // test results reporter to use
63 | // possible values: 'dots', 'progress'
64 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
65 | reporters: ['dots', 'karma-remap-istanbul'],
66 |
67 | // web server port
68 | port: 9876,
69 |
70 | // enable / disable colors in the output (reporters and logs)
71 | colors: true,
72 |
73 | // level of logging
74 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
75 | logLevel: config.LOG_INFO,
76 |
77 | // enable / disable watching file and executing tests whenever any file changes
78 | autoWatch: false,
79 |
80 | // start these browsers
81 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
82 | browsers: ['PhantomJS'],
83 |
84 | // Continuous Integration mode
85 | // if true, Karma captures browsers, runs the tests and exits
86 | singleRun: true
87 | });
88 | };
89 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const istanbul = require('istanbul');
4 | const remap = require('remap-istanbul/lib/remap');
5 | const writeReport = require('remap-istanbul/lib/writeReport');
6 |
7 | function KarmaRemapIstanbul(baseReporterDecorator, logger, config) {
8 | baseReporterDecorator(this);
9 |
10 | const log = logger.create('reporter.remap-istanbul');
11 |
12 | const remapIstanbulReporterConfig = config.remapIstanbulReporter || {};
13 | const reports = remapIstanbulReporterConfig.reports || {};
14 | const remapOptions = remapIstanbulReporterConfig.remapOptions || {};
15 | const reportOptions = remapIstanbulReporterConfig.reportOptions || {};
16 |
17 | const coverageMap = new WeakMap();
18 |
19 | const baseReporterOnRunStart = this.onRunStart;
20 | this.onRunStart = function () {
21 | baseReporterOnRunStart.apply(this, arguments);
22 | };
23 |
24 | this.onBrowserComplete = function (browser, result) {
25 | if (!result || !result.coverage) {
26 | return;
27 | }
28 |
29 | coverageMap.set(browser, result.coverage);
30 | };
31 |
32 | let reportFinished = () => {};
33 |
34 | const baseReporterOnRunComplete = this.onRunComplete;
35 | this.onRunComplete = function (browsers) {
36 | baseReporterOnRunComplete.apply(this, arguments);
37 |
38 | // Collect the unmapped coverage information for all browsers in this run
39 | const unmappedCoverage = (() => {
40 | const collector = new istanbul.Collector();
41 |
42 | browsers.forEach(browser => {
43 | const coverage = coverageMap.get(browser);
44 | coverageMap.delete(browser);
45 |
46 | if (!coverage) {
47 | return;
48 | }
49 |
50 | collector.add(coverage);
51 | });
52 |
53 | return collector.getFinalCoverage();
54 | })();
55 |
56 | let sourceStore = istanbul.Store.create('memory');
57 |
58 | const collector = remap(unmappedCoverage, Object.assign({
59 | sources: sourceStore
60 | }, remapOptions));
61 |
62 | if (Object.keys(sourceStore.map).length === 0) {
63 | sourceStore = undefined;
64 | }
65 |
66 | Promise.all(Object.keys(reports).map(reportType => {
67 | const destination = reports[reportType];
68 |
69 | log.debug('Writing coverage to %s', destination);
70 |
71 | return writeReport(collector, reportType, reportOptions, destination, sourceStore);
72 | })).catch(err => {
73 | log.error(err);
74 | }).then(() => {
75 | collector.dispose();
76 |
77 | reportFinished();
78 |
79 | // Reassign `onExit` to just call `done` since all reports have been written
80 | this.onExit = function (done) {
81 | done();
82 | };
83 | });
84 | };
85 |
86 | this.onExit = function (done) {
87 | // Reports have not been written, so assign `done` to `reportFinished`
88 | reportFinished = done;
89 | };
90 | }
91 |
92 | KarmaRemapIstanbul.$inject = ['baseReporterDecorator', 'logger', 'config'];
93 |
94 | module.exports = {
95 | 'reporter:karma-remap-istanbul': ['type', KarmaRemapIstanbul]
96 | };
97 |
--------------------------------------------------------------------------------
/test/index.spec.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const chai = require('chai');
4 | const karma = require('karma');
5 | const rimraf = require('rimraf');
6 |
7 | const expect = chai.expect;
8 | const karmaRemapIstanbul = require('../');
9 |
10 | const FIXTURES_OUTPUT = path.join(__dirname, '/fixtures/outputs');
11 |
12 | function createServer(config) {
13 | config = config || {};
14 | return new karma.Server(Object.assign({
15 | configFile: path.join(__dirname, '/karma.conf.js'),
16 | plugins: [
17 | 'karma-mocha',
18 | 'karma-phantomjs-launcher',
19 | 'karma-webpack',
20 | 'karma-sourcemap-loader',
21 | karmaRemapIstanbul
22 | ]
23 | }, config), () => {});
24 | }
25 |
26 | describe('karma-remap-istanbul', () => {
27 | beforeEach(() => {
28 | rimraf.sync(FIXTURES_OUTPUT);
29 | });
30 |
31 | it('should generate a remapped coverage report', done => {
32 | const server = createServer();
33 | server.start();
34 | server.on('run_complete', () => {
35 | setTimeout(() => { // hacky workaround to make sure the file has been written
36 | const summary = JSON.parse(fs.readFileSync(path.join(FIXTURES_OUTPUT, 'coverage.json')));
37 | expect(summary.total).to.deep.equal({
38 | lines: {
39 | total: 6,
40 | covered: 5,
41 | skipped: 0,
42 | pct: 83.33
43 | },
44 | statements: {
45 | total: 7,
46 | covered: 6,
47 | skipped: 0,
48 | pct: 85.71
49 | },
50 | functions: {
51 | total: 3,
52 | covered: 2,
53 | skipped: 0,
54 | pct: 66.67
55 | },
56 | branches: {
57 | total: 0,
58 | covered: 0,
59 | skipped: 0,
60 | pct: 100
61 | },
62 | linesCovered: {
63 | 1: 2,
64 | 3: 1,
65 | 4: 1,
66 | 7: 1,
67 | 8: 0,
68 | 11: 1
69 | }
70 | });
71 | done();
72 | }, 300);
73 | });
74 | });
75 |
76 | it('should allow files to be excluded', done => {
77 | const server = createServer({
78 | remapIstanbulReporter: {
79 | reports: {
80 | 'json-summary': path.join(FIXTURES_OUTPUT, 'coverage.json')
81 | },
82 | remapOptions: {
83 | exclude: 'example'
84 | }
85 | }
86 | });
87 | server.start();
88 |
89 | server.on('run_complete', () => {
90 | setTimeout(() => { // hacky workaround to make sure the file has been written
91 | const summary = JSON.parse(fs.readFileSync(path.join(FIXTURES_OUTPUT, 'coverage.json')));
92 | expect(summary.total).to.deep.equal({
93 | lines: {
94 | total: 0,
95 | covered: 0,
96 | skipped: 0,
97 | pct: 100
98 | },
99 | statements: {
100 | total: 0,
101 | covered: 0,
102 | skipped: 0,
103 | pct: 100
104 | },
105 | functions: {
106 | total: 0,
107 | covered: 0,
108 | skipped: 0,
109 | pct: 100
110 | },
111 | branches: {
112 | total: 0,
113 | covered: 0,
114 | skipped: 0,
115 | pct: 100
116 | },
117 | linesCovered: {}
118 | });
119 | done();
120 | }, 300);
121 | });
122 | });
123 | });
124 |
--------------------------------------------------------------------------------