├── .babelrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── isparta ├── package.json ├── renovate.json ├── screenshot.png ├── src ├── cli │ ├── ArgParser.js │ ├── commands │ │ └── cover.js │ └── index.js ├── instrumenter.js └── isparta.js └── test ├── ArgParser.js ├── _helpers.js ├── api.js ├── bin.js ├── fixtures ├── es6-classes │ ├── actual.js │ ├── compiled.js │ └── expectedCover.js └── virgin │ ├── actual.js │ ├── compiled.js │ └── expectedCover.js ├── index.js └── mocha.opts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "plugins": [ 6 | "transform-object-rest-spread" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: false 3 | language: node_js 4 | node_js: 5 | - 'stable' 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull: 2 | 3 |
4 |
5 | 6 |

Deprecated for istanbuljs/nyc

7 | 8 |
9 |
10 | 11 | :skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull::skull: 12 | 13 |
14 |
15 |
16 |
17 | 18 | # isparta [![Build Status][travis-image]][travis-url] [![NPM version][npm-image]][npm-url] [![No Maintenance Intended][unmaintained-image]][unmaintained-url] 19 | 20 | > Isparta is a code coverage tool for ES6 using [babel](https://github.com/babel/babel). 21 | 22 | Its intention is to be used with [karma](http://karma-runner.github.io/) and [karma-coverage](https://github.com/karma-runner/karma-coverage), which provides code coverage reports using [istanbul](https://github.com/gotwarlost/istanbul). 23 | 24 | [CHANGELOG](https://github.com/douglasduteil/isparta/releases) 25 | 26 | ## Installation 27 | 28 | Isparta can be installed using 29 | 30 | ```sh 31 | $ npm install --save-dev isparta 32 | ``` 33 | 34 | ## Usage 35 | 36 | **Not all the istanbul command/options are available with isparta** 37 | **Consult `isparta -h` for more information** 38 | 39 | Here is an example to run a coverage over mocha tests 40 | 41 | ```bash 42 | babel-node node_modules/isparta/bin/isparta cover --report text --report html node_modules/mocha/bin/_mocha -- --reporter dot 43 | ``` 44 | 45 | [douglasduteil/study-node-path-es6](https://github.com/douglasduteil/study-node-path-es6) demo the working cli 46 | 47 | ### With Karma 48 | 49 | To use isparta, set the [instrumenter](https://github.com/karma-runner/karma-coverage/blob/master/README.md#instrumenter) for the JavaScript file type to `isparta`. 50 | 51 | ```js 52 | coverageReporter: { 53 | // configure the reporter to use isparta for JavaScript coverage 54 | // Only on { "karma-coverage": "douglasduteil/karma-coverage#next" } 55 | instrumenters: { isparta : require('isparta') }, 56 | instrumenter: { 57 | '**/*.js': 'isparta' 58 | } 59 | } 60 | ``` 61 | 62 | But can customize the babel options thanks to my [fork](https://github.com/douglasduteil/karma-coverage/tree/next) 63 | 64 | ```js 65 | 66 | // Note that you ".babelrc" will be the default options for babel. 67 | var babelMoreOptions = { presets: 'es2015' }; 68 | 69 | // [...] 70 | 71 | coverageReporter: { 72 | // configure the reporter to use isparta for JavaScript coverage 73 | // Only on { "karma-coverage": "douglasduteil/karma-coverage#next" } 74 | instrumenters: { isparta : require('isparta') }, 75 | instrumenter: { 76 | '**/*.js': 'isparta' 77 | }, 78 | instrumenterOptions: { 79 | isparta: { babel : babelMoreOptions } 80 | } 81 | } 82 | ``` 83 | 84 | ![](screenshot.png) 85 | 86 | ## License 87 | 88 | Copyright © 2014 Douglas Duteil 89 | This work is free. You can redistribute it and/or modify it under the 90 | terms of the Do What The Fuck You Want To Public License, Version 2, 91 | as published by Sam Hocevar. See the LICENCE file for more details. 92 | 93 | [npm-url]: https://npmjs.org/package/isparta 94 | [npm-image]: http://img.shields.io/npm/v/isparta.svg 95 | [travis-url]: http://travis-ci.org/douglasduteil/isparta 96 | [travis-image]: http://travis-ci.org/douglasduteil/isparta.svg?branch=master 97 | [unmaintained-image]: http://unmaintained.tech/badge.svg 98 | [unmaintained-url]: http://unmaintained.tech 99 | -------------------------------------------------------------------------------- /bin/isparta: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/cli'); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isparta", 3 | "version": "4.1.1", 4 | "description": "A code coverage tool for ES6 (babel)", 5 | "main": "./lib/isparta", 6 | "files": [ 7 | "bin/", 8 | "lib/" 9 | ], 10 | "keywords": [ 11 | "karma", 12 | "karma-coverage", 13 | "karma-traceur-preprocessor", 14 | "istanbul", 15 | "6to5", 16 | "babel", 17 | "es6", 18 | "harmony" 19 | ], 20 | "directories": { 21 | "lib": "./lib" 22 | }, 23 | "bin": { 24 | "isparta": "./bin/isparta" 25 | }, 26 | "author": { 27 | "name": "Douglas Duteil", 28 | "email": "douglasduteil@gmail.com" 29 | }, 30 | "homepage": "http://github.com/douglasduteil/isparta", 31 | "bugs": { 32 | "url": "http://github.com/douglasduteil/isparta/issues" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git://github.com/douglasduteil/isparta.git" 37 | }, 38 | "license": "WTFPL", 39 | "dependencies": { 40 | "babel-core": "^6.1.4", 41 | "escodegen": "^1.6.1", 42 | "esprima": "^4.0.0", 43 | "istanbul": "0.4.5", 44 | "mkdirp": "^0.5.0", 45 | "nomnomnomnom": "^2.0.0", 46 | "object-assign": "^4.0.1", 47 | "source-map": "^0.5.0", 48 | "which": "^1.0.9" 49 | }, 50 | "devDependencies": { 51 | "babel-cli": "^6.1.4", 52 | "babel-plugin-transform-object-rest-spread": "^6.1.4", 53 | "babel-polyfill": "^6.1.4", 54 | "babel-preset-es2015": "^6.1.4", 55 | "chai": "^4.0.0", 56 | "douglasduteil...shelltest": "^2.0.0", 57 | "mocha": "^5.0.0", 58 | "nth": "^0.1.2", 59 | "sinon": "^1.17.2", 60 | "sinon-chai": "^3.0.0" 61 | }, 62 | "scripts": { 63 | "dist": "babel src --out-dir lib", 64 | "watch": "npm run dist -- -w", 65 | "test": "mocha", 66 | "prepublish": "npm run dist" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "pinVersions": false 7 | } 8 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/douglasduteil/isparta/af82484af6e7a6657f7aa9ec4bc9eb229d50777e/screenshot.png -------------------------------------------------------------------------------- /src/cli/ArgParser.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import nomnom from 'nomnomnomnom'; 4 | 5 | // 6 | 7 | 8 | export default ArgParser 9 | 10 | // 11 | 12 | function ArgParser (commands) { 13 | const parser = nomnom(); 14 | 15 | parser.command('cover') 16 | .help("transparently adds coverage information to a node command. Saves coverage.json and reports at the end of execution") 17 | 18 | .option('cmd', { 19 | required: true, 20 | position: 1, 21 | help: 'ES6 js files to cover (using babel)' 22 | }) 23 | 24 | .option('config', { 25 | metavar: '', 26 | help: 'the configuration file to use, defaults to .istanbul.yml' 27 | }) 28 | .option('default-excludes', { 29 | flag: true, 30 | help: 'apply default excludes [ **/node_modules/**, **/test/**, **/tests/** ]' 31 | }) 32 | .option('excludes', { 33 | abbr: 'x', 34 | default: [], 35 | help: 'one or more fileset patterns e.g. "**/vendor/**"', 36 | list: true, 37 | metavar: '' 38 | }) 39 | .option('report', { 40 | default: 'lcv', 41 | metavar: '', 42 | list: true, 43 | help: 'report format' 44 | }) 45 | .option('root', { 46 | metavar: '', 47 | help: 'the root path to look for files to instrument' 48 | }) 49 | .option('include', { 50 | default: ['**/*.js'], 51 | metavar: '', 52 | list: true, 53 | abbr: 'i', 54 | help: 'one or more fileset patterns e.g. \'**/*.js\'' 55 | }) 56 | .option('verbose', { 57 | flag: true, 58 | abbr: 'v', 59 | help: 'verbose mode' 60 | }) 61 | .option('include-all-sources', { 62 | flag: true, 63 | help: 'instrument all unused sources after running tests' 64 | }) 65 | .callback(commands.cover) 66 | ; 67 | 68 | return parser 69 | } 70 | -------------------------------------------------------------------------------- /src/cli/commands/cover.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {existsSync, writeFileSync} from 'fs'; 4 | import {Instrumenter} from '../../instrumenter'; 5 | import {hook, Collector, Reporter, matcherFor, config as configuration} from 'istanbul'; 6 | import mkdirp from 'mkdirp'; 7 | import Module from 'module'; 8 | import assign from 'object-assign'; 9 | import path from 'path'; 10 | import which from 'which'; 11 | 12 | // 13 | 14 | export default coverCmd 15 | 16 | // 17 | 18 | function coverCmd (opts) { 19 | let config = overrideConfigWith(opts); 20 | let istanbulCoveragePath = path.resolve(config.reporting.dir()); 21 | let reporter = new Reporter(config, istanbulCoveragePath); 22 | 23 | let { cmd } = opts; 24 | let cmdArgs = opts['--'] || []; 25 | 26 | if (!existsSync(cmd)) { 27 | try { 28 | cmd = which.sync(cmd); 29 | } catch (ex) { 30 | return processEnding(`Unable to resolve file [${cmd}]`); 31 | } 32 | } else { 33 | cmd = path.resolve(cmd); 34 | } 35 | 36 | if (opts.verbose) console.error('Isparta options : \n ', opts); 37 | 38 | let excludes = config.instrumentation.excludes(true); 39 | enableHooks(); 40 | 41 | 42 | //// 43 | 44 | function overrideConfigWith (opts) { 45 | let overrides = { 46 | verbose: opts.verbose, 47 | instrumentation: { 48 | root: opts.root, 49 | 'default-excludes': opts['default-excludes'], 50 | excludes: opts.excludes, 51 | 'include-all-sources': opts['include-all-sources'], 52 | // preload-sources is deprecated 53 | // TODO(douglasduteil): remove this option 54 | 'preload-sources': opts['preload-sources'] 55 | }, 56 | reporting: { 57 | reports: opts.report, 58 | print: opts.print, 59 | dir: opts.dir 60 | }, 61 | hooks: { 62 | 'hook-run-in-context': opts['hook-run-in-context'], 63 | 'post-require-hook': opts['post-require-hook'], 64 | 'handle-sigint': opts['handle-sigint'] 65 | } 66 | }; 67 | 68 | return configuration.loadFile(opts.config, overrides); 69 | } 70 | 71 | function enableHooks () { 72 | opts.reportingDir = path.resolve(config.reporting.dir()); 73 | mkdirp.sync(opts.reportingDir); 74 | reporter.addAll(config.reporting.reports()); 75 | 76 | if (config.reporting.print() !== 'none') { 77 | switch (config.reporting.print()) { 78 | case 'detail': 79 | reporter.add('text'); 80 | break; 81 | case 'both': 82 | reporter.add('text'); 83 | reporter.add('text-summary'); 84 | break; 85 | default: 86 | reporter.add('text-summary'); 87 | break; 88 | } 89 | } 90 | 91 | excludes.push(path.relative(process.cwd(), path.join(opts.reportingDir, '**', '*'))); 92 | 93 | matcherFor({ 94 | root: config.instrumentation.root() || process.cwd(), 95 | includes: opts.include || config.instrumentation.extensions() 96 | .map((ext) => '**/*' + ext), 97 | excludes: excludes 98 | }, (err, matchFn) => { 99 | if (err) { 100 | return processEnding(err); 101 | } 102 | 103 | prepareCoverage(matchFn); 104 | runCommandFn(); 105 | }); 106 | } 107 | 108 | 109 | function prepareCoverage (matchFn) { 110 | let coverageVar = `$$cov_${Date.now()}$$`; 111 | let instrumenter = new Instrumenter({ coverageVariable: coverageVar }); 112 | let transformer = instrumenter.instrumentSync.bind(instrumenter); 113 | 114 | hook.hookRequire(matchFn, transformer, assign({ verbose: opts.verbose }, config.instrumentation.config)); 115 | 116 | global[coverageVar] = {}; 117 | 118 | if (config.hooks.handleSigint()) { 119 | process.once('SIGINT', process.exit); 120 | } 121 | 122 | process.once('exit', (code) => { 123 | if (code) { 124 | process.exit(code); 125 | } 126 | let file = path.resolve(opts.reportingDir, 'coverage.json'); 127 | let cov, collector; 128 | 129 | if (typeof global[coverageVar] === 'undefined' || Object.keys(global[coverageVar]).length === 0) { 130 | console.error('No coverage information was collected, exit without writing coverage information'); 131 | return; 132 | } else { 133 | cov = global[coverageVar]; 134 | } 135 | 136 | mkdirp.sync(opts.reportingDir); 137 | if (config.reporting.print() !== 'none') { 138 | console.error(Array(80 + 1).join('=')); 139 | console.error(`Writing coverage object [${file}]`); 140 | } 141 | writeFileSync(file, JSON.stringify(cov), 'utf8'); 142 | collector = new Collector(); 143 | collector.add(cov); 144 | if (config.reporting.print() !== 'none') { 145 | console.error(`Writing coverage reports at [${opts.reportingDir}]`); 146 | console.error(Array(80 + 1).join('=')); 147 | } 148 | reporter.write(collector, true, processEnding); 149 | }); 150 | 151 | if (config.instrumentation.includeAllSources()) { 152 | matchFn.files.forEach(function (file) { 153 | if (opts.verbose) { console.error('Preload ' + file); } 154 | try { 155 | require(file); 156 | } catch (ex) { 157 | if (opts.verbose) { 158 | console.error('Unable to preload ' + file); 159 | } 160 | // swallow 161 | } 162 | }); 163 | } 164 | 165 | } 166 | 167 | function runCommandFn () { 168 | process.argv = ["node", cmd].concat(cmdArgs); 169 | if (opts.verbose) { 170 | console.log('Running: ' + process.argv.join(' ')); 171 | } 172 | process.env.running_under_istanbul = 1; 173 | Module.runMain(cmd, null, true); 174 | } 175 | } 176 | 177 | // 178 | 179 | function processEnding (err) { 180 | if (err) { 181 | console.error(err); 182 | process.exit(1); 183 | } 184 | process.exit(0); 185 | } 186 | -------------------------------------------------------------------------------- /src/cli/index.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {existsSync, statSync} from 'fs'; 4 | 5 | // 6 | 7 | import cover from './commands/cover' 8 | import ArgParser from './ArgParser' 9 | 10 | // 11 | 12 | ArgParser({ 13 | cover: runCoverCommand 14 | }) 15 | .parse(); 16 | 17 | // 18 | 19 | function runCoverCommand (opts) { 20 | const files = opts._.slice(1).reduce(function (memo, file) { 21 | return memo.concat(lookupFile(file) || []) 22 | }, []); 23 | 24 | opts.include = opts.include.concat(files); 25 | cover(opts); 26 | } 27 | 28 | function lookupFile (path) { 29 | if (existsSync(path)) { 30 | let stat = statSync(path); 31 | if (stat.isFile()) return path; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/instrumenter.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import istanbul from 'istanbul'; 4 | import {transform as babelTransform} from 'babel-core'; 5 | 6 | import { parse } from 'esprima'; 7 | import escodegen from 'escodegen'; 8 | 9 | import {SourceMapConsumer} from 'source-map'; 10 | 11 | const POSITIONS = ['start', 'end']; 12 | 13 | export class Instrumenter extends istanbul.Instrumenter { 14 | 15 | constructor (options = {}) { 16 | super(); 17 | 18 | istanbul.Instrumenter.call(this, options); 19 | 20 | this.babelOptions = { 21 | sourceMap: true, 22 | ...(options && options.babel || {}) 23 | }; 24 | } 25 | 26 | instrumentSync (code, fileName) { 27 | 28 | const result = this._r = 29 | babelTransform(code, { ...this.babelOptions, filename: fileName }); 30 | this._babelMap = new SourceMapConsumer(result.map); 31 | 32 | // PARSE 33 | let program = parse(result.code, { 34 | loc: true, 35 | range: true, 36 | tokens: this.opts.preserveComments, 37 | comment: true 38 | }); 39 | 40 | if (this.opts.preserveComments) { 41 | program = escodegen 42 | .attachComments(program, program.comments, program.tokens); 43 | } 44 | 45 | return this.instrumentASTSync(program, fileName, code); 46 | } 47 | 48 | getPreamble (sourceCode, emitUseStrict) { 49 | 50 | [['s', 'statementMap'], ['f', 'fnMap'], ['b', 'branchMap']] 51 | .forEach(([metricName, metricMapName]) => { 52 | let [metrics, metricMap] = [ 53 | this.coverState[metricName], 54 | this.coverState[metricMapName] 55 | ]; 56 | let transformFctName = `_${metricMapName}Transformer`; 57 | let transformedMetricMap = this[transformFctName](metricMap, metrics) 58 | this.coverState[metricMapName] = transformedMetricMap; 59 | }) 60 | 61 | return super.getPreamble(sourceCode, emitUseStrict); 62 | } 63 | 64 | //// 65 | 66 | _statementMapTransformer (metrics) { 67 | return Object.keys(metrics) 68 | .map((index) => metrics[index]) 69 | .map((statementMeta) => { 70 | let [location] = this._getMetricOriginalLocations([statementMeta]); 71 | return location; 72 | }) 73 | .reduce(this._arrayToArrayLikeObject, {}); 74 | } 75 | 76 | _fnMapTransformer (metrics) { 77 | return Object.keys(metrics) 78 | .map((index) => metrics[index]) 79 | .map((fnMeta) => { 80 | let [loc] = this._getMetricOriginalLocations([fnMeta.loc]); 81 | 82 | // Force remove the last skip key 83 | if (fnMeta.skip === undefined) { 84 | delete fnMeta.skip; 85 | if (loc.skip !== undefined) { 86 | fnMeta.skip = loc.skip; 87 | } 88 | } 89 | 90 | return { ...fnMeta, loc }; 91 | }) 92 | .reduce(this._arrayToArrayLikeObject, {}); 93 | } 94 | 95 | _branchMapTransformer (metrics) { 96 | return Object.keys(metrics) 97 | .map((index) => metrics[index]) 98 | .map((branchMeta) => { 99 | return { 100 | ...branchMeta, 101 | ...{ 102 | locations: this._getMetricOriginalLocations(branchMeta.locations) 103 | } 104 | }; 105 | }) 106 | .reduce(this._arrayToArrayLikeObject, {}); 107 | } 108 | 109 | //// 110 | 111 | _getMetricOriginalLocations (metricLocations = []) { 112 | let o = { line: 0, column: 0 }; 113 | 114 | return metricLocations 115 | .map((generatedPositions) => { 116 | return [ 117 | this._getOriginalPositionsFor(generatedPositions), 118 | generatedPositions 119 | ] 120 | }) 121 | .map(([{start, end}, generatedPosition]) => { 122 | let postitions = [start.line, start.column, end.line, end.column]; 123 | let isValid = postitions.every((n) => n !== null); 124 | 125 | // Matches behavior in _fnMapTransformer above. 126 | if (generatedPosition.skip === undefined) { 127 | delete generatedPosition.skip; 128 | } 129 | 130 | return isValid 131 | ? { ...generatedPosition, start, end } 132 | : { start: o, end: o, skip: true }; 133 | }) 134 | } 135 | 136 | _getOriginalPositionsFor (generatedPositions = { start: {}, end: {} }) { 137 | return POSITIONS 138 | .map((position) => [generatedPositions[position], position]) 139 | .reduce((originalPositions, [generatedPosition, position]) => { 140 | let originalPosition = this._babelMap.originalPositionFor(generatedPosition); 141 | // remove extra keys 142 | delete originalPosition.name; 143 | delete originalPosition.source; 144 | originalPositions[position] = originalPosition; 145 | return originalPositions; 146 | }, {}); 147 | } 148 | 149 | _arrayToArrayLikeObject (arrayLikeObject, item, index) { 150 | arrayLikeObject[index + 1] = item; 151 | return arrayLikeObject; 152 | }; 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/isparta.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | export {Store, Collector, hook, Report, config, Reporter, utils, matcherFor, Writer, ContentWriter, FileWriter, _yuiLoadHook, TreeSummarizer, assetsDir} from 'istanbul'; 4 | export {Instrumenter} from './instrumenter'; 5 | export {VERSION} from '../package.json'; 6 | -------------------------------------------------------------------------------- /test/ArgParser.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {expect, default as chai} from 'chai' 4 | import sinon from 'sinon' 5 | import sinonChai from 'sinon-chai' 6 | 7 | import ArgParser from '../src/cli/ArgParser' 8 | 9 | // 10 | 11 | chai.use(sinonChai); 12 | 13 | // 14 | 15 | describe('ArgParser', function () { 16 | var commands; 17 | var parser; 18 | 19 | beforeEach(function () { 20 | commands = { 21 | cover: sinon.spy() 22 | }; 23 | 24 | parser = ArgParser(commands).nocolors(); 25 | parser.print = sinon.stub(); 26 | parser.printer(parser.print); 27 | }); 28 | 29 | describe('no command', function () { 30 | it('should require a command', function () { 31 | parser.parse([]); 32 | expect(parser.print).to.have.been.calledWithMatch(/command argument is required/); 33 | }); 34 | }); 35 | 36 | describe('cover command', function () { 37 | 38 | it('should require a file', function () { 39 | parser.parse(['cover']); 40 | expect(parser.print).to.have.been.calledWithMatch(/cmd argument is require/); 41 | }); 42 | 43 | it('should run with default options', function () { 44 | parser.parse(['cover', 'foo.js']); 45 | expect(commands.cover).to.have.been.calledWithMatch({ 46 | cmd: 'foo.js', 47 | excludes: [], 48 | include: ['**/*.js'], 49 | report: 'lcv' 50 | }); 51 | }); 52 | 53 | describe('--config', function () { 54 | it('should throw without value', function () { 55 | parser.parse(['cover', 'foo.js', '--config']); 56 | expect(parser.print).to.have.been.calledWithMatch(/'--config' expects a value/); 57 | }); 58 | 59 | it('should run with the config in option', function () { 60 | parser.parse(['cover', 'foo.js', '--config', 'myconfig.yml']); 61 | expect(commands.cover).to.have.been.calledWithMatch({ 62 | config: 'myconfig.yml' 63 | }); 64 | }); 65 | }); 66 | 67 | describe('--default-excludes', function () { 68 | it('should run with the default-excludes in option', function () { 69 | parser.parse(['cover', 'foo.js', '--default-excludes']); 70 | expect(commands.cover).to.have.been.calledWithMatch({ 71 | 'default-excludes': true 72 | }); 73 | }); 74 | }); 75 | 76 | describe('--excludes', function () { 77 | it('should throw without value', function () { 78 | parser.parse(['cover', 'foo.js', '--excludes']); 79 | expect(parser.print).to.have.been.calledWithMatch(/'--excludes' expects a value/); 80 | }); 81 | 82 | it('should run with the excludes in option', function () { 83 | parser.parse(['cover', 'foo.js', '--excludes', 'excluded.file']); 84 | expect(commands.cover).to.have.been.calledWithMatch({ 85 | excludes: ['excluded.file'] 86 | }); 87 | }); 88 | 89 | it('should run with the multiple flag', function () { 90 | parser.parse(['cover', 'foo.js', '--excludes', 'x', '--excludes', 'y', '--excludes', 'z']); 91 | expect(commands.cover).to.have.been.calledWithMatch({ 92 | excludes: ['x', 'y', 'z'] 93 | }); 94 | }); 95 | 96 | it('should work with the -x alias', function () { 97 | parser.parse(['cover', 'foo.js', '-x', 'excluded.file']); 98 | expect(commands.cover).to.have.been.calledWithMatch({ 99 | excludes: ['excluded.file'] 100 | }); 101 | }); 102 | }); 103 | 104 | describe('--report', function () { 105 | it('should throw without value', function () { 106 | parser.parse(['cover', 'foo.js', '--report']); 107 | expect(parser.print).to.have.been.calledWithMatch(/'--report' expects a value/); 108 | }); 109 | 110 | it('should run with the report in option', function () { 111 | parser.parse(['cover', 'foo.js', '--report', 'report-format']); 112 | expect(commands.cover).to.have.been.calledWithMatch({ 113 | report: ['report-format'] 114 | }); 115 | }); 116 | 117 | it('should run with the multiple flag', function () { 118 | parser.parse(['cover', 'foo.js', '--report', 'x', '--report', 'y', '--report', 'z']); 119 | expect(commands.cover).to.have.been.calledWithMatch({ 120 | report: ['x', 'y', 'z'] 121 | }); 122 | }); 123 | }); 124 | 125 | describe('--include', function () { 126 | it('should throw without value', function () { 127 | parser.parse(['cover', 'foo.js', '--include']); 128 | expect(parser.print).to.have.been.calledWithMatch(/'--include' expects a value/); 129 | }); 130 | 131 | it('should run with the include in option', function () { 132 | parser.parse(['cover', 'foo.js', '--include', 'excluded.file']); 133 | expect(commands.cover).to.have.been.calledWithMatch({ 134 | include: ['excluded.file'] 135 | }); 136 | }); 137 | 138 | it('should run with the multiple flag', function () { 139 | parser.parse(['cover', 'foo.js', '--include', 'x', '--include', 'y', '--include', 'z']); 140 | expect(commands.cover).to.have.been.calledWithMatch({ 141 | include: ['x', 'y', 'z'] 142 | }); 143 | }); 144 | 145 | it('should work with the -i alias', function () { 146 | parser.parse(['cover', 'foo.js', '-i', 'included.file']); 147 | expect(commands.cover).to.have.been.calledWithMatch({ 148 | include: ['included.file'] 149 | }); 150 | }); 151 | }); 152 | 153 | describe('--verbose', function () { 154 | it('should run with the verbose in option', function () { 155 | parser.parse(['cover', 'foo.js', '--verbose']); 156 | expect(commands.cover).to.have.been.calledWithMatch({ 157 | verbose: true 158 | }); 159 | }); 160 | }); 161 | 162 | describe('--include-all-sources', function () { 163 | it('should run with the include-all-sources in option', function () { 164 | parser.parse(['cover', 'foo.js', '--include-all-sources']); 165 | expect(commands.cover).to.have.been.calledWithMatch({ 166 | 'include-all-sources': true 167 | }); 168 | }); 169 | }); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /test/_helpers.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import fs from 'fs' 4 | import path from 'path' 5 | 6 | const FIXTURES_PATH = path.join(__dirname, 'fixtures'); 7 | 8 | export function readFile (filename) { 9 | if (fs.existsSync(filename)) { 10 | var file = fs.readFileSync(filename, 'utf8').trim(); 11 | file = file.replace(/\r\n/g, '\n'); 12 | return file; 13 | } else { 14 | return ''; 15 | } 16 | } 17 | 18 | export function getFixturesTest () { 19 | return fs 20 | .readdirSync(FIXTURES_PATH) 21 | .filter((fixtureName) => fixtureName[0] !== '.') 22 | .map(createFixtureDescription); 23 | 24 | // 25 | 26 | function createFixtureDescription (fixtureName) { 27 | let [actual, expectedCover] = [ 28 | { filename: 'actual.js', access: readFile }, 29 | { filename: 'expectedCover.js', access: require } 30 | ] 31 | .map(({filename, access}) => { 32 | let relLoc = path.join(fixtureName, filename); 33 | return { 34 | loc: relLoc, 35 | code: access(path.join(FIXTURES_PATH, relLoc)), 36 | filename: filename 37 | } 38 | }); 39 | 40 | 41 | return { name: fixtureName, actual, expectedCover }; 42 | } 43 | 44 | } 45 | 46 | // 47 | 48 | export function extractCodeExpect (content, location) { 49 | if (!(content && location)) return ''; 50 | 51 | let { start, end } = location; 52 | 53 | return start.line === end.line ? 54 | 55 | extractExpectInLine(content[start.line - 1], location) : 56 | 57 | Array.from( 58 | new Array(end.line - start.line + 1), 59 | (x, i) => { 60 | let lastLine = start.line + i === end.line; 61 | return extractExpectInLine( 62 | content[start.line - 1 + i], 63 | { 64 | start: { column: !i ? start.column : 0 }, 65 | end: { column: lastLine ? end.column : Infinity } 66 | } 67 | ); 68 | } 69 | ).join('\n'); 70 | } 71 | 72 | function extractExpectInLine (line = '', { start = { column: 0 }, end = { column: 0 }}) { 73 | return line.substring(start.column, end.column); 74 | } 75 | -------------------------------------------------------------------------------- /test/api.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {expect} from 'chai'; 4 | 5 | // using require here instead of import to avoid babel messing with it 6 | const istanbul = require('istanbul'); 7 | const isparta = require('../'); 8 | 9 | describe('API', function () { 10 | it('should include all the public istanbul symbols', function () { 11 | expect(istanbul).to.contain.all.keys(isparta); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/bin.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import {expect} from 'chai' 4 | import shelltest from 'douglasduteil...shelltest' 5 | 6 | import pkg from '../package.json' 7 | 8 | // 9 | 10 | var BIN = pkg.bin['isparta']; 11 | 12 | describe('Isparta bin', function () { 13 | this.slow(1500); 14 | 15 | it('should throws with no commands', function (done) { 16 | shelltest() 17 | .cmd(BIN) 18 | .expect(1) 19 | .expect('stdout', /command argument is required/) 20 | .end(function (err) { 21 | if (err.name === 'AssertionError') { return done(err); } 22 | expect(err).to.be.an.instanceof(Error); 23 | expect(err.message).to.match(/Command failed:/); 24 | done(); 25 | }) 26 | }); 27 | 28 | describe('command "cover"', function () { 29 | 30 | it('should throws with no cmd option', function (done) { 31 | shelltest() 32 | .cmd(BIN + ' cover') 33 | .expect(1) 34 | .expect('stdout', /cmd argument is required/) 35 | .end(function (err) { 36 | if (err.name === 'AssertionError') { return done(err); } 37 | expect(err).to.be.an.instanceof(Error); 38 | expect(err.message).to.match(/Command failed:/); 39 | done(); 40 | }) 41 | }); 42 | 43 | it('should throws if the cmd to cover is not found', function (done) { 44 | shelltest() 45 | .cmd(BIN + ' cover foo.js') 46 | .expect(1) 47 | .expect('stderr', /Unable to resolve file \[foo\.js\]/) 48 | .end(function (err) { 49 | if (err.name === 'AssertionError') { return done(err); } 50 | expect(err).to.be.an.instanceof(Error); 51 | expect(err.message).to.match(/Command failed:/); 52 | done(); 53 | }) 54 | }); 55 | 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/fixtures/es6-classes/actual.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | var Animal = class { 4 | sayHi() { 5 | return 'Hi, I am a ' + this.type() + '.'; 6 | } 7 | 8 | sayOther() { 9 | return 'WAT?!'; 10 | } 11 | 12 | static getName() { 13 | return 'Animal'; 14 | } 15 | } 16 | 17 | export {Animal}; 18 | -------------------------------------------------------------------------------- /test/fixtures/es6-classes/compiled.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 10 | 11 | // 12 | 13 | var Animal = function () { 14 | function Animal() { 15 | _classCallCheck(this, Animal); 16 | } 17 | 18 | _createClass(Animal, [{ 19 | key: 'sayHi', 20 | value: function sayHi() { 21 | return 'Hi, I am a ' + this.type() + '.'; 22 | } 23 | }, { 24 | key: 'sayOther', 25 | value: function sayOther() { 26 | return 'WAT?!'; 27 | } 28 | }], [{ 29 | key: 'getName', 30 | value: function getName() { 31 | return 'Animal'; 32 | } 33 | }]); 34 | 35 | return Animal; 36 | }(); 37 | 38 | exports.Animal = Animal; 39 | 40 | -------------------------------------------------------------------------------- /test/fixtures/es6-classes/expectedCover.js: -------------------------------------------------------------------------------- 1 | // Compiled to ./compiled.js 2 | 3 | const lostStatment = { 4 | start: { line: 0, column: 0 }, 5 | end: { line: 0, column: 0 } 6 | }; 7 | 8 | const skippedStatment = { 9 | ...lostStatment, 10 | skip: true 11 | }; 12 | 13 | //// 14 | 15 | function generateNSkipedStatement(n) { 16 | return Array.from(Array(n)).map(() => skippedStatment); 17 | } 18 | 19 | function generateNSkipedFunction(...lostFnData) { 20 | return lostFnData.map(fnData => { 21 | return { ...fnData, loc: { ...(fnData.loc || skippedStatment) } }; 22 | }); 23 | } 24 | 25 | function generateNSkipedBranch(...lostBranchData) { 26 | return lostBranchData.map(branchData => { 27 | return { 28 | ...branchData, 29 | locations: [{ ...skippedStatment }, { ...skippedStatment }] 30 | }; 31 | }); 32 | } 33 | 34 | //// 35 | 36 | module.exports = { 37 | statementMap: [] 38 | .concat(generateNSkipedStatement(19)) 39 | .concat([ 40 | // 20th statement 41 | { 42 | start: { line: 3, column: 0 }, 43 | end: { line: 3, column: 0 } 44 | }, 45 | // 21th statement 46 | { 47 | start: { line: 3, column: 4 }, 48 | end: { line: 3, column: 4 } 49 | }, 50 | // 22th statement 51 | { 52 | start: { line: 3, column: 4 }, 53 | end: { line: 3, column: 4 } 54 | }, 55 | // 23th statement 56 | { 57 | start: { line: 3, column: 4 }, 58 | end: { line: 3, column: 4 } 59 | }, 60 | // 24th statement 61 | { 62 | start: { line: 5, column: 4 }, 63 | end: { line: 5, column: 4 } 64 | }, 65 | // 25th statement 66 | { 67 | start: { line: 9, column: 4 }, 68 | end: { line: 9, column: 4 } 69 | }, 70 | // 26th statement 71 | { 72 | start: { line: 13, column: 4 }, 73 | end: { line: 13, column: 4 } 74 | }, 75 | // 26th statement 76 | { 77 | start: { line: 3, column: 4 }, 78 | end: { line: 3, column: 4 } 79 | } 80 | ]) 81 | .concat(generateNSkipedStatement(1)), 82 | fnMap: [] 83 | .concat( 84 | generateNSkipedFunction( 85 | { name: '(anonymous_1)', line: 7, skip: true }, 86 | { name: 'defineProperties', line: 7, skip: true }, 87 | { name: '(anonymous_3)', line: 7, skip: true }, 88 | { name: '_classCallCheck', line: 9, skip: true } 89 | ) 90 | ) 91 | .concat([ 92 | // 5th fn 93 | { 94 | name: '(anonymous_5)', 95 | line: 13, 96 | loc: { 97 | start: { line: 3, column: 4 }, 98 | end: { line: 3, column: 4 } 99 | } 100 | } 101 | ]) 102 | .concat( 103 | generateNSkipedFunction({ 104 | name: 'Animal', 105 | line: 14, 106 | loc: { 107 | start: { line: 3, column: 4 }, 108 | end: { line: 3, column: 4 } 109 | } 110 | }) 111 | ) 112 | .concat( 113 | generateNSkipedFunction({ 114 | name: 'sayHi', 115 | line: 20, 116 | loc: { 117 | start: { line: 3, column: 4 }, 118 | end: { line: 4, column: 10 } 119 | } 120 | }) 121 | ) 122 | .concat( 123 | generateNSkipedFunction({ 124 | name: 'sayOther', 125 | line: 25, 126 | loc: { 127 | start: { line: 3, column: 4 }, 128 | end: { line: 8, column: 13 } 129 | } 130 | }) 131 | ) 132 | .concat( 133 | generateNSkipedFunction({ 134 | name: 'getName', 135 | line: 30, 136 | loc: { 137 | start: { line: 3, column: 4 }, 138 | end: { line: 12, column: 19 } 139 | } 140 | }) 141 | ), 142 | 143 | branchMap: generateNSkipedBranch( 144 | // 1th branch 145 | { line: 7, type: 'binary-expr' }, 146 | // 2th branch 147 | { line: 7, type: 'if' }, 148 | // 3th branch 149 | { line: 7, type: 'if' }, 150 | // 4th branch 151 | { line: 7, type: 'if' }, 152 | // 5th branch 153 | { line: 9, type: 'if' } 154 | ) 155 | }; 156 | -------------------------------------------------------------------------------- /test/fixtures/virgin/actual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | ONE.foo = function (bar) { 4 | return baz(bar ? 0 : 1); 5 | }; 6 | 7 | /* istanbul ignore next */ 8 | if (true) { 9 | var a = 5; 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/virgin/compiled.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | ONE.foo = function (bar) { 4 | return baz(bar ? 0 : 1); 5 | }; 6 | 7 | /* istanbul ignore next */ 8 | if (true) { 9 | var a = 5; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/virgin/expectedCover.js: -------------------------------------------------------------------------------- 1 | // Compiled to 2 | 3 | // 1. "use strict"; 4 | // 2. 5 | // 3. ONE.foo = function (bar) { 6 | // 4. return baz(bar ? 0 : 1); 7 | // 5. }; 8 | // 6. 9 | // 7. /* istanbul ignore next */ 10 | // 8. if (true) { 11 | // 9. var a = 5; 12 | // 10. } 13 | 14 | module.exports = { 15 | statementMap: [ 16 | { 17 | // 3. 18 | start: { line: 3, column: 0 }, 19 | end: { line: 3, column: 0 } 20 | }, 21 | { 22 | // 4. 23 | start: { line: 4, column: 2 }, 24 | end: { line: 4, column: 2 } 25 | }, 26 | { 27 | // 5. }; 28 | // 6. 29 | // 7. /* istanbul ignore next */ 30 | // 8. if (true) { 31 | // 9. var a = 5; 32 | // 10. } 33 | start: { line: 8, column: 0 }, 34 | end: { line: 10, column: 1 }, 35 | skip: true 36 | }, 37 | { 38 | // 9. var a = 5; 39 | start: { line: 9, column: 2 }, 40 | end: { line: 9, column: 2 }, 41 | skip: true 42 | } 43 | ], 44 | 45 | fnMap: [ 46 | { 47 | // 3. ONE.foo = function (bar) { 48 | name: '(anonymous_1)', 49 | line: 3, 50 | loc: { 51 | start: { line: 3, column: 10 }, 52 | end: { line: 3, column: 25 } 53 | } 54 | } 55 | ], 56 | 57 | branchMap: [ 58 | { 59 | // 4. return baz(bar ? 0 : 1); 60 | line: 4, 61 | type: 'cond-expr', 62 | locations: [ 63 | { 64 | start: { line: 4, column: 19 }, 65 | end: { line: 4, column: 13 } 66 | }, 67 | { 68 | start: { line: 4, column: 23 }, 69 | end: { line: 4, column: 9 } 70 | } 71 | ] 72 | }, 73 | { 74 | // 5. }; 75 | // 6. 76 | // 7. /* istanbul ignore next */ 77 | // 8. if (true) { 78 | line: 8, 79 | type: 'if', 80 | locations: [ 81 | { 82 | start: { line: 8, column: 0 }, 83 | end: { line: 8, column: 0 }, 84 | skip: true 85 | }, 86 | { 87 | start: { line: 8, column: 0 }, 88 | end: { line: 8, column: 0 }, 89 | skip: true 90 | } 91 | ] 92 | } 93 | ] 94 | }; 95 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Mocha from 'mocha' 4 | let {Test, Suite} = Mocha; 5 | 6 | import {expect} from 'chai' 7 | import {appendSuffix as nth} from 'nth' 8 | 9 | 10 | import {getFixturesTest, extractCodeExpect} from './_helpers.js'; 11 | 12 | import {Instrumenter, Reporter} from '../src/isparta'; 13 | 14 | //// 15 | 16 | const MAP_TYPES = [ 17 | { 18 | name: 'statement', 19 | get fullName () { return `${this.name}Map` }, 20 | getLocations: (loc) => [loc] 21 | }, 22 | { 23 | name: 'fn', 24 | get fullName () { return `${this.name}Map` }, 25 | getLocations: (fn) => [fn.loc] 26 | }, 27 | { 28 | name: 'branch', 29 | get fullName () { return `${this.name}Map` }, 30 | getLocations: (br) => br.locations 31 | } 32 | ]; 33 | 34 | //// 35 | 36 | let instumenterSuite = describe("Isparta instrumenter", function () { 37 | before(generateSourceMapTest); 38 | 39 | it('should generate the tests', function () { 40 | expect(instumenterSuite.suites.length).to.be.above(0); 41 | }); 42 | 43 | }); 44 | 45 | function generateSourceMapTest (allDone) { 46 | 47 | let instrumenter = new Instrumenter(); 48 | const fixturesToTest = getFixturesTest(); 49 | const done = after(fixturesToTest.length, allDone); 50 | 51 | fixturesToTest.map((fixtureTest) => { 52 | 53 | let {name, actual} = fixtureTest; 54 | 55 | instrumenter.instrument(actual.code, actual.loc, (err) => { 56 | if (err) { throw err; } 57 | 58 | let fixtureSuite = Suite.create(instumenterSuite, `when ${name}`); 59 | fixtureSuite.afterEach('display code snippet diff', displaySnippetError); 60 | 61 | MAP_TYPES 62 | .map(testCoverMaps(instrumenter.coverState, fixtureTest)) 63 | .reduce((coverTests, tests) => coverTests.concat(tests), []) 64 | .forEach((test) => fixtureSuite.addTest(test)); 65 | 66 | 67 | done(); 68 | }); 69 | }); 70 | 71 | 72 | } 73 | 74 | function displaySnippetError () { 75 | if (!this.error) { 76 | return; 77 | } 78 | 79 | let codeLines = this.error.codeLines; 80 | 81 | this.error.expectedLocation.forEach((expectedLoc, i) => { 82 | let actualLoc = this.error.actualLocation[i]; 83 | let expectCode = extractCodeExpect(codeLines, expectedLoc); 84 | let actualCode = extractCodeExpect(codeLines, actualLoc); 85 | //console.log('<<<<<<<< expectCode | ', expectCode); 86 | //console.log('<<<<<<<< actualCode | ', actualCode); 87 | expect(actualCode).to.equal(expectCode); 88 | }); 89 | 90 | } 91 | 92 | 93 | function testCoverMaps (maps, fixtureTest) { 94 | 95 | var actualCodeLines = fixtureTest.actual.code.split('\n'); 96 | 97 | return function testCoverMap (type) { 98 | let mapKey = `${type.name}Map`; 99 | let map = values(maps[mapKey] || {}); 100 | 101 | return map.map((loc, i) => { 102 | 103 | return new Test(`should localize the ${nth(i + 1)} ${type.name}`, locationIt); 104 | 105 | //// 106 | 107 | function locationIt () { 108 | 109 | this.error = { 110 | actualLocation: type.getLocations(loc), 111 | expectedLocation: type.getLocations( 112 | fixtureTest.expectedCover.code[mapKey][i] || {} 113 | ) || [], 114 | codeLines: actualCodeLines 115 | }; 116 | 117 | expect(loc).to.eql(fixtureTest.expectedCover.code[mapKey][i] || {}, 118 | `Expect the ${nth(i + 1)} ${type.name}s to be deeply equal.`); 119 | 120 | this.error = null; 121 | 122 | } 123 | 124 | }); 125 | } 126 | 127 | } 128 | 129 | // 130 | 131 | // Minimal Lodash util functions 132 | function values (arr) { return Object.keys(arr).map(key => arr[key] || {}); } 133 | function after (n, func) { 134 | return function () { 135 | if (--n < 1) { return func.apply(this, arguments); } 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers babel:babel-core/register 2 | --require babel-polyfill 3 | --------------------------------------------------------------------------------