Previous posts

This post is part of a series of build chain automation examples. Previous posts include:

In this post we’ll cover off adding source maps and code coverage in to the mix.

Source for this example is available here

Source maps FTW

There aren’t too many (if any) downsides to outputting source maps and as a dear friend and colleague pointed out - this can be done in production too, behind auth if necessary.

Among other awesome outcomes, doing so will allow us to leverage some tooling to provide high quality code coverage metrics for your typescript code. Who wouldn’t want that?

So hence our first step: generate our javascript files with source maps

// A simple source map output example
var gulp = require('gulp');
var sourcemaps = require('gulp-sourcemaps');
var ts = require('gulp-typescript');

gulp.task('build', function() {
   return gulp.src('./src/**/*.ts', { base: '.' })
   .pipe(sourcemaps.init()) //get ready for some maps...
   .pipe(ts(tsProject))
   .pipe(sourcemaps.write()) //BAM
   .pipe(gulp.dest('.'));
});

Istanbul and remap-istanbul

One of the challenges for getting reliable source code coverage with TypeScript is the fact that most javascript coverage frameworks are only focussed on the emitted JS - not your original source. One such framework is the often used, instanbul.

The good people at sitepen.com have remedied this situation with a package called remap-istanbul. They have been using Intern as their test runner - but I’ll show you how you can lean on Instanbul directly.

To add this into our build chain created in previous posts, we’ll need the following packages:

#globals
npm install -g istanbul
#locals
npm install remap-istanbul

More info: gulp-istanbul github documentation:

It would be completely ok just to use instanbul, however I feel that the outputted javasceript that typescript generates can get verbose quickly, making it harder to make quick judgements about code coverage. For instance, here is what a contrived example will look like when covered:

istanbul sans remap

75% Coverage isn’t great right? This was generated by the following TS source:

// contrived example
export default class MathHelper {
    static sum(numbers: number[]) {
        return numbers.reduce((prev, curr) => prev + curr);
    }
}

//in a test file far, far away...
describe('`sum` method', () => {
    it('should provide a sum of a number array', () => {
       const numbers: number[] = [,0,-1,1,42, null as number];
       expect( MathHelper.sum( numbers ) ).to.eq( 42 );
    });
});

Remap to source

The solution here is to use remap-istanbul to rewrite the coverage data. This is done via the source maps we generated earlier, to map back to the original source. It remains a clever idea in my opinion

Alas, our gulpfile complexity increases to accomodate:

// source map task omitted for brevity - see above

var gulp = require('gulp');
var mocha = require('gulp-mocha');
var istanbul = require('gulp-istanbul');
var remapIstanbul = require('remap-istanbul/lib/gulpRemapIstanbul');

gulp.task('test:cover', ['test:instrument'], function() {
    return gulp.src('./transpiled/**/*Tests.js') //take our transpiled test source
    .pipe(mocha({ui:'bdd'})) //runs tests
    .pipe(istanbul.writeReports({
        reporters: [ 'json' ] //this yields a basic non-sourcemapped coverage.json file
    })).on('end', remapCoverageFiles); //perform a remap
});

//using remap-istanbul we can point our coverage data back to the original ts files
function remapCoverageFiles() {
    return gulp.src('./coverage/coverage-final.json')
    .pipe(remapIstanbul({
        basePath: '.',
        reports: {
            'html': './coverage',
            'text-summary': null,
            'lcovonly': './coverage/lcov.info'
        }
    }));
}

Running gulp we should see the following:

gulp test and coverage run

Our result will now show 100% coverage and the associated typescript source - as expected.

gulp test and coverage run