├── .npmignore ├── .gitignore ├── demo ├── diff.png ├── screen.png ├── fullPageReload.spec.js ├── firstSuccess.spec.js ├── duplicate.spec.js ├── skipping.spec.js ├── demo.spec.js ├── demo2.spec.js ├── fail.spec.js ├── mocha.spec.js ├── aDemo.spec.js ├── failinOneBrowser.spec.js ├── karma.conf.js ├── karma.mocha.conf.js └── karma.autowatch.conf.js ├── typings.json ├── .travis.yml ├── jsconfig.json ├── symbols.js ├── .jshintrc ├── LICENSE ├── package.json ├── CHANGELOG.md ├── README.md ├── Gruntfile.js └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | Gruntfile.js 3 | /demo -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | atlassian-ide-plugin.xml 4 | node_modules 5 | typings 6 | *.iml -------------------------------------------------------------------------------- /demo/diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephfrazier/karma-mocha-reporter/master/demo/diff.png -------------------------------------------------------------------------------- /demo/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephfrazier/karma-mocha-reporter/master/demo/screen.png -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "chalk": "registry:dt/chalk#0.4.0+20160317120654", 4 | "karma": "registry:dt/karma#0.13.9+20160501125529" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | node_js: 7 | - '4' 8 | - '5' 9 | - '6' 10 | - 'stable' 11 | before_script: 12 | - export DISPLAY=:99.0 13 | - sh -e /etc/init.d/xvfb start 14 | - npm install -g grunt-cli 15 | -------------------------------------------------------------------------------- /demo/fullPageReload.spec.js: -------------------------------------------------------------------------------- 1 | describe('thing', function () { 2 | it('should do stuff', function () { 3 | window.location.reload(); 4 | expect(1 + 1).toEqual(2); 5 | }); 6 | 7 | it('should do other stuff', function () { 8 | expect(1 + 1).toEqual(2); 9 | }); 10 | }); -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es5", 6 | "module": "commonjs", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /symbols.js: -------------------------------------------------------------------------------- 1 | // used symbols from here: https://github.com/sindresorhus/log-symbols 2 | 'use strict'; 3 | 4 | var main = { 5 | info: 'ℹ', 6 | success: '✔', 7 | warning: '⚠', 8 | error: '✖' 9 | }; 10 | 11 | var win = { 12 | info: 'i', 13 | success: '√', 14 | warning: '‼', 15 | error: '×' 16 | }; 17 | 18 | module.exports = process.platform === 'win32' ? win : main; -------------------------------------------------------------------------------- /demo/firstSuccess.spec.js: -------------------------------------------------------------------------------- 1 | if (navigator.userAgent.match(/firefox/i)) { 2 | describe('Firefox tests', function() { 3 | it('this would only be reported when printFirstSuccess is true', function() { 4 | console.log('firefox test'); 5 | }); 6 | }); 7 | } 8 | 9 | describe('Other tests', function() { 10 | it('this should be always reported', function() { 11 | console.log('hello world'); 12 | }); 13 | }); -------------------------------------------------------------------------------- /demo/duplicate.spec.js: -------------------------------------------------------------------------------- 1 | describe('thing', function () { 2 | it('should do stuff', function () { 3 | expect(1 + 1).toEqual(2); 4 | }); 5 | 6 | it('should do stuff', function () { 7 | expect(2 + 1).toEqual(3); 8 | }); 9 | 10 | it('should do stuff', function () { 11 | expect(2 + 1).toEqual(3); 12 | }); 13 | 14 | describe('thing', function () { 15 | it('should do stuff', function () { 16 | expect(1 + 1).toEqual(2); 17 | }); 18 | 19 | it('should do stuff', function () { 20 | expect(2 + 1).toEqual(3); 21 | }); 22 | }); 23 | }); -------------------------------------------------------------------------------- /demo/skipping.spec.js: -------------------------------------------------------------------------------- 1 | describe('Some suite', function () { 2 | fit('Enabled test', function () { 3 | expect(true).toBeTruthy(); 4 | }); 5 | 6 | fdescribe('Enabled suite', function () { 7 | it('should be skipped', function () { 8 | expect(true).toBeTruthy(); 9 | }); 10 | }); 11 | 12 | it('Skipped test', function () { 13 | expect(true).toBeTruthy(); 14 | }); 15 | 16 | describe('Skipped suite', function () { 17 | it('something', function () { 18 | }); 19 | 20 | describe('somethingElse', function () { 21 | }); 22 | }); 23 | }); -------------------------------------------------------------------------------- /demo/demo.spec.js: -------------------------------------------------------------------------------- 1 | describe('2 Demo test suite', function () { 2 | it('should assert true', function () { 3 | expect(true).toBeTruthy(); 4 | }); 5 | 6 | xdescribe('ignored', function () { 7 | it('should be skipped', function () { 8 | expect(true).toBeTruthy(); 9 | }); 10 | }); 11 | 12 | it('should assert true 1', function () { 13 | expect(true).toBeTruthy(); 14 | }); 15 | 16 | describe('Reserved words', function () { 17 | it('toString', function () { 18 | var list = [1, 2]; 19 | expect(list.toString()).toBe('1,2'); 20 | }); 21 | 22 | describe('constructor', function () { 23 | it('hasOwnProperty', function () { 24 | expect(true).toBeTruthy(); 25 | }); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "forin": true, 6 | "immed": true, 7 | "latedef": true, 8 | "newcap": true, 9 | "noarg": true, 10 | "noempty": true, 11 | "nonew": true, 12 | "undef": true, 13 | "unused": true, 14 | "loopfunc": true, 15 | "indent": 4, 16 | "quotmark": "single", 17 | "node": true, 18 | "globals": { 19 | "assert": false, 20 | "beforeEach": false, 21 | "describe": false, 22 | "fdescribe": false, 23 | "expect": false, 24 | "fit": false, 25 | "it": false, 26 | "navigator": false, 27 | "runs": false, 28 | "waitsFor": false, 29 | "xit": false, 30 | "xdescribe": false, 31 | "window": false, 32 | "spyOn": false 33 | } 34 | } -------------------------------------------------------------------------------- /demo/demo2.spec.js: -------------------------------------------------------------------------------- 1 | describe('Demo2 test suite', function () { 2 | describe('inner suite 1', function () { 3 | describe('inner suite 2', function () { 4 | it('should assert true 2', function () { 5 | expect(true).toBeTruthy(); 6 | }); 7 | }); 8 | 9 | it('should assert true 1', function () { 10 | expect(true).toBeTruthy(); 11 | }); 12 | }); 13 | 14 | it('1 should assert true', function () { 15 | expect(true).toBeTruthy(); 16 | }); 17 | 18 | xdescribe('ignored', function () { 19 | xit('should be skipped', function () { 20 | expect(true).toBeTruthy(); 21 | }); 22 | }); 23 | 24 | it('2 should fail', function () { 25 | expect(true).toBeFalsy(); 26 | }); 27 | 28 | it('3 should assert true', function () { 29 | expect(true).toBeTruthy(); 30 | }); 31 | 32 | it('4 should assert true', function () { 33 | expect(true).toBeTruthy(); 34 | }); 35 | 36 | it('5 should throw Error', function () { 37 | throw new TypeError('wayne'); 38 | }); 39 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013-2016 Litixsoft GmbH 2 | Licensed under the MIT license. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. -------------------------------------------------------------------------------- /demo/fail.spec.js: -------------------------------------------------------------------------------- 1 | describe('Fail demo test suite', function () { 2 | it('should assert true', function () { 3 | expect(true).toBeFalsy(); 4 | }); 5 | 6 | it('should assert true 1', function () { 7 | expect(true).toBeFalsy(); 8 | }); 9 | 10 | it('should show all failures for this silly test', function () { 11 | expect(1).toBe(0); 12 | expect(2).toBe(0); 13 | expect(3).toBe(0); 14 | expect(4).toBe(0); 15 | }); 16 | 17 | describe('test', function () { 18 | var foo, bar; 19 | 20 | beforeEach(function () { 21 | foo = { 22 | setBar: function (value) { 23 | bar = value; 24 | } 25 | }; 26 | 27 | spyOn(foo, 'setBar'); 28 | 29 | foo.setBar(123); 30 | foo.setBar(456, 'another param'); 31 | }); 32 | 33 | it('tracks that the spy was called', function () { 34 | expect(foo.setBar).toHaveBeenCalled(); 35 | expect(foo.setBar.calls.count()).toEqual(3); 36 | }); 37 | 38 | it('tracks all the arguments of its calls', function () { 39 | expect(foo.setBar).toHaveBeenCalledWith(123); 40 | expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); 41 | }); 42 | }); 43 | }); -------------------------------------------------------------------------------- /demo/mocha.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('A mocha test suite', function () { 4 | 5 | it('should work', function () { 6 | var expected = 'foo'; 7 | var actual = 'foo'; 8 | expect(actual).to.equal(expected); 9 | }); 10 | 11 | it('should show a multiline string diff', function () { 12 | var expected = 'b\na'; 13 | var actual = 'a\nb'; 14 | expect(actual).to.equal(expected); 15 | }); 16 | 17 | it('should show a string diff', function () { 18 | var expected = 'foo'; 19 | var actual = 'foo bar'; 20 | expect(actual).to.equal(expected); 21 | }); 22 | 23 | it('should show an array diff', function () { 24 | var expected = [ 25 | 'foo', 26 | 'bar' 27 | ]; 28 | var actual = [ 29 | 'bar', 30 | 'baz' 31 | ]; 32 | expect(actual).to.deep.equal(expected); 33 | }); 34 | 35 | it('should show an object diff', function () { 36 | var expected = { 37 | foo: 42, 38 | bar: 1764, 39 | baz: { 40 | qux: 'bleep', 41 | norf: 'bloop' 42 | } 43 | }; 44 | var actual = { 45 | bar: 1764, 46 | baz: { 47 | norf: 'bloop' 48 | }, 49 | random: true 50 | }; 51 | expect(actual).to.deep.equal(expected); 52 | }); 53 | 54 | describe('Test Suite 1 (with a skipped test)', function () { 55 | // case 1 56 | xit('should fail', function () { 57 | assert.isTrue(false); 58 | }); 59 | 60 | // case 2 61 | it('should pass', function () { 62 | assert.isTrue(true); 63 | }); 64 | }); 65 | 66 | describe('Test Suite 2', function () { 67 | it('should fail', function () { 68 | assert.isTrue(false); 69 | }); 70 | }); 71 | }); -------------------------------------------------------------------------------- /demo/aDemo.spec.js: -------------------------------------------------------------------------------- 1 | describe('A demo test suite', function () { 2 | it('1.1.0 should assert true', function () { 3 | expect(true).toBeTruthy(); 4 | }); 5 | 6 | describe('with an inner suite', function () { 7 | it('1.2.0 should assert true', function () { 8 | expect(true).toBeTruthy(); 9 | }); 10 | 11 | describe('with an inner suite 2', function () { 12 | it('1.3.0 should assert true the slow async', function (done) { 13 | expect(true).toBeTruthy(); 14 | 15 | setTimeout(function () { 16 | expect(true).toBeTruthy(); 17 | done(); 18 | }, 1500); 19 | }); 20 | 21 | it('1.3.1 should assert true the slow async', function (done) { 22 | expect(true).toBeTruthy(); 23 | 24 | setTimeout(function () { 25 | expect(true).toBeTruthy(); 26 | done(); 27 | }, 1500); 28 | }); 29 | 30 | it('1.3.2 should fail', function () { 31 | expect(true).toBeFalsy(); 32 | }); 33 | }); 34 | 35 | it('1.2.1 should assert true', function () { 36 | expect(true).toBeTruthy(); 37 | }); 38 | 39 | it('1.2.2 should assert true the slow async', function (done) { 40 | expect(true).toBeTruthy(); 41 | 42 | setTimeout(function () { 43 | expect(true).toBeTruthy(); 44 | done(); 45 | }, 1500); 46 | }); 47 | }); 48 | 49 | xdescribe('ignored', function () { 50 | it('should be skipped', function () { 51 | expect(true).toBeTruthy(); 52 | }); 53 | }); 54 | 55 | xit('should be skipped', function () { 56 | expect(true).toBeTruthy(); 57 | }); 58 | 59 | it('1.4.0 should fail', function () { 60 | expect(true).toBeFalsy(); 61 | }); 62 | }); -------------------------------------------------------------------------------- /demo/failinOneBrowser.spec.js: -------------------------------------------------------------------------------- 1 | describe('Fail in one browser only test suite', function () { 2 | function goodFunction() { 3 | return true; 4 | } 5 | 6 | function badFunction() { 7 | throw new Error('Oh no!'); 8 | } 9 | 10 | function badFunctionFor(agent) { 11 | if (navigator.userAgent.indexOf(agent) !== -1) { 12 | throw new Error('Oh no! What\'s wrong with ' + agent); 13 | } 14 | } 15 | 16 | describe('Reproduce mocha-reporter output', function() { 17 | it('fails in all browsers', function() { 18 | badFunction(); 19 | }); 20 | 21 | it('works in all browsers', function() { 22 | goodFunction(); 23 | }); 24 | 25 | describe('Reproduce mocha-reporter output nested', function() { 26 | it('fails in all browsers', function() { 27 | badFunction(); 28 | }); 29 | 30 | it('works in all browsers', function() { 31 | goodFunction(); 32 | }); 33 | 34 | it('fails in Chrome', function() { 35 | badFunctionFor('Chrome'); 36 | }); 37 | 38 | it('fails in PhantomJS', function() { 39 | badFunctionFor('PhantomJS'); 40 | }); 41 | }); 42 | 43 | it('fails in Chrome', function() { 44 | badFunctionFor('Chrome'); 45 | }); 46 | 47 | it('fails in PhantomJS', function() { 48 | badFunctionFor('PhantomJS'); 49 | }); 50 | }); 51 | 52 | describe('Reproduce mocha-reporter output run 2', function() { 53 | it('fails in all browsers', function() { 54 | badFunction(); 55 | }); 56 | 57 | it('works in all browsers', function() { 58 | goodFunction(); 59 | }); 60 | 61 | it('fails in Chrome', function() { 62 | badFunctionFor('Chrome'); 63 | }); 64 | 65 | it('fails in PhantomJS', function() { 66 | badFunctionFor('PhantomJS'); 67 | }); 68 | }); 69 | }); -------------------------------------------------------------------------------- /demo/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | // base path, that will be used to resolve files and exclude 4 | basePath: '../', 5 | 6 | frameworks: ['jasmine', 'detectBrowsers'], 7 | 8 | // list of files / patterns to load in the browser 9 | files: [ 10 | 'demo/*.spec.js' 11 | ], 12 | 13 | // use dots reporter, as travis terminal does not support escaping sequences 14 | // possible values: 'dots', 'progress' 15 | // CLI --reporters progress 16 | reporters: ['mocha'], 17 | 18 | // web server port 19 | // CLI --port 9876 20 | port: 9876, 21 | 22 | // enable / disable colors in the output (reporters and logs) 23 | // CLI --colors --no-colors 24 | colors: true, 25 | 26 | // level of logging 27 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 28 | // CLI --log-level debug 29 | logLevel: config.LOG_INFO, 30 | 31 | // enable / disable watching file and executing tests whenever any file changes 32 | // CLI --auto-watch --no-auto-watch 33 | autoWatch: false, 34 | 35 | // Start these browsers, currently available: 36 | // - Chrome 37 | // - ChromeCanary 38 | // - Firefox 39 | // - Opera 40 | // - Safari (only Mac) 41 | // - PhantomJS 42 | // - IE (only Windows) 43 | // CLI --browsers Chrome,Firefox,Safari 44 | browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'], 45 | 46 | // If browser does not capture in given timeout [ms], kill it 47 | // CLI --capture-timeout 5000 48 | captureTimeout: 20000, 49 | 50 | // Auto run tests on start (when browsers are captured) and exit 51 | // CLI --single-run --no-single-run 52 | singleRun: true, 53 | 54 | // report which specs are slower than 500ms 55 | // CLI --report-slower-than 500 56 | reportSlowerThan: 500 57 | }); 58 | }; -------------------------------------------------------------------------------- /demo/karma.mocha.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | // base path, that will be used to resolve files and exclude 4 | basePath: '../', 5 | 6 | frameworks: ['mocha', 'chai'], 7 | 8 | // list of files / patterns to load in the browser 9 | files: [ 10 | 'demo/mocha.spec.js' 11 | ], 12 | 13 | mochaReporter: { 14 | showDiff: true 15 | }, 16 | 17 | // use dots reporter, as travis terminal does not support escaping sequences 18 | // possible values: 'dots', 'progress' 19 | // CLI --reporters progress 20 | reporters: ['mocha'], 21 | 22 | // web server port 23 | // CLI --port 9876 24 | port: 9876, 25 | 26 | // enable / disable colors in the output (reporters and logs) 27 | // CLI --colors --no-colors 28 | colors: true, 29 | 30 | // level of logging 31 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 32 | // CLI --log-level debug 33 | logLevel: config.LOG_INFO, 34 | 35 | // enable / disable watching file and executing tests whenever any file changes 36 | // CLI --auto-watch --no-auto-watch 37 | autoWatch: false, 38 | 39 | // Start these browsers, currently available: 40 | // - Chrome 41 | // - ChromeCanary 42 | // - Firefox 43 | // - Opera 44 | // - Safari (only Mac) 45 | // - PhantomJS 46 | // - IE (only Windows) 47 | // CLI --browsers Chrome,Firefox,Safari 48 | browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'], 49 | 50 | // If browser does not capture in given timeout [ms], kill it 51 | // CLI --capture-timeout 5000 52 | captureTimeout: 20000, 53 | 54 | // Auto run tests on start (when browsers are captured) and exit 55 | // CLI --single-run --no-single-run 56 | singleRun: true, 57 | 58 | // report which specs are slower than 500ms 59 | // CLI --report-slower-than 500 60 | reportSlowerThan: 500 61 | }); 62 | }; -------------------------------------------------------------------------------- /demo/karma.autowatch.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | // base path, that will be used to resolve files and exclude 4 | basePath: '../', 5 | 6 | frameworks: ['jasmine', 'detectBrowsers'], 7 | 8 | // list of files / patterns to load in the browser 9 | files: [ 10 | 'demo/*.spec.js' 11 | ], 12 | 13 | // use dots reporter, as travis terminal does not support escaping sequences 14 | // possible values: 'dots', 'progress' 15 | // CLI --reporters progress 16 | reporters: ['mocha'], 17 | 18 | mochaReporter: { 19 | output: 'autowatch' 20 | }, 21 | 22 | // web server port 23 | // CLI --port 9876 24 | port: 9876, 25 | 26 | // enable / disable colors in the output (reporters and logs) 27 | // CLI --colors --no-colors 28 | colors: true, 29 | 30 | // level of logging 31 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 32 | // CLI --log-level debug 33 | logLevel: config.LOG_INFO, 34 | 35 | // enable / disable watching file and executing tests whenever any file changes 36 | // CLI --auto-watch --no-auto-watch 37 | autoWatch: true, 38 | 39 | // Start these browsers, currently available: 40 | // - Chrome 41 | // - ChromeCanary 42 | // - Firefox 43 | // - Opera 44 | // - Safari (only Mac) 45 | // - PhantomJS 46 | // - IE (only Windows) 47 | // CLI --browsers Chrome,Firefox,Safari 48 | browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'], 49 | 50 | // If browser does not capture in given timeout [ms], kill it 51 | // CLI --capture-timeout 5000 52 | captureTimeout: 20000, 53 | 54 | // Auto run tests on start (when browsers are captured) and exit 55 | // CLI --single-run --no-single-run 56 | singleRun: false, 57 | 58 | // report which specs are slower than 500ms 59 | // CLI --report-slower-than 500 60 | reportSlowerThan: 500, 61 | 62 | plugins: [ 63 | 'karma-jasmine', 64 | 'karma-mocha-reporter', 65 | 'karma-chrome-launcher', 66 | 'karma-firefox-launcher', 67 | 'karma-ie-launcher', 68 | 'karma-safari-launcher', 69 | 'karma-phantomjs-launcher', 70 | 'karma-detect-browsers' 71 | ] 72 | }); 73 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "karma-mocha-reporter", 3 | "description": "Karma reporter with mocha style logging.", 4 | "version": "2.2.2", 5 | "homepage": "http://www.litixsoft.de/index.php?lang=en#modules", 6 | "author": "Litixsoft GmbH (http://www.litixsoft.de)", 7 | "maintainers": [ 8 | "Timo Liebetrau ", 9 | "Marco Kannenberg ", 10 | "Sven Bernstein ", 11 | "Dominic Sachs ", 12 | "Thomas Scheibe ", 13 | "Mike Alig ", 14 | "Joerg Raschke ", 15 | "Andreas Krummsdorf " 16 | ], 17 | "contributors": [ 18 | "Artem Baguinski ", 19 | "Brendan Nee ", 20 | "Túbal Martín", 21 | "M-Koch", 22 | "Ryan P Kilby", 23 | "christian-fei", 24 | "Clay Anderson", 25 | "Minh Son Nguyen", 26 | "Adam Craven", 27 | "Teddy Sterne" 28 | ], 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/litixsoft/karma-mocha-reporter.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/litixsoft/karma-mocha-reporter/issues" 35 | }, 36 | "keywords": [ 37 | "karma-plugin", 38 | "karma-reporter", 39 | "mocha", 40 | "chai", 41 | "diff", 42 | "symbols" 43 | ], 44 | "license": "MIT", 45 | "licenses": [ 46 | { 47 | "type": "MIT", 48 | "url": "https://github.com/litixsoft/karma-mocha-reporter/blob/master/LICENSE" 49 | } 50 | ], 51 | "main": "index.js", 52 | "scripts": { 53 | "test": "grunt test", 54 | "demo": "grunt demo --force" 55 | }, 56 | "devDependencies": { 57 | "chai": "^3.5.0", 58 | "grunt": "^1.0.1", 59 | "grunt-bump": "^0.8.0", 60 | "grunt-contrib-copy": "^1.0.0", 61 | "grunt-contrib-jshint": "^1.0.0", 62 | "grunt-conventional-changelog": "^6.1.0", 63 | "grunt-karma": "^2.0.0", 64 | "grunt-shell": "^2.0.0", 65 | "jasmine-core": "^2.5.2", 66 | "karma": ">=0.13", 67 | "karma-chai": "^0.1.0", 68 | "karma-chrome-launcher": "*", 69 | "karma-detect-browsers": "^2.1.0", 70 | "karma-firefox-launcher": "*", 71 | "karma-ie-launcher": "*", 72 | "karma-jasmine": "*", 73 | "karma-mocha": "*", 74 | "karma-opera-launcher": "*", 75 | "karma-phantomjs-launcher": "*", 76 | "karma-safari-launcher": "*", 77 | "karma-safaritechpreview-launcher": "*", 78 | "load-grunt-tasks": "^3.5.2", 79 | "mocha": "^3.0.2", 80 | "phantomjs-prebuilt": "^2.1.12" 81 | }, 82 | "dependencies": { 83 | "chalk": "1.1.3" 84 | }, 85 | "peerDependencies": { 86 | "karma": ">=0.13" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [2.2.2](https://github.com/litixsoft/karma-mocha-reporter/compare/v2.2.0...v2.2.2) (2017-01-19) 3 | 4 | 5 | ### Features 6 | 7 | * add option printFirstSuccess ([9e2f342](https://github.com/litixsoft/karma-mocha-reporter/commit/9e2f342)) 8 | 9 | 10 | 11 | 12 | ## [2.2.1](https://github.com/litixsoft/karma-mocha-reporter/compare/v2.2.0...v2.2.1) (2016-11-18) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * log can be an empty array ([5f08bbd](https://github.com/litixsoft/karma-mocha-reporter/commit/5f08bbd)) 18 | * log can be empty ([008569a](https://github.com/litixsoft/karma-mocha-reporter/commit/008569a)) 19 | 20 | 21 | 22 | 23 | # [2.2.0](https://github.com/litixsoft/karma-mocha-reporter/compare/v2.1.0...v2.2.0) (2016-09-19) 24 | 25 | 26 | ### Features 27 | 28 | * add option "maxLogLines" to control the number of lines which are printed for the failures ([0c484df](https://github.com/litixsoft/karma-mocha-reporter/commit/0c484df)), closes [#75](https://github.com/litixsoft/karma-mocha-reporter/issues/75) 29 | 30 | 31 | 32 | 33 | # [2.1.0](https://github.com/litixsoft/karma-mocha-reporter/compare/v2.0.5...v2.1.0) (2016-07-31) 34 | 35 | 36 | ### Features 37 | 38 | * Add options `symbols` to overwrite the default symbols ([66e0454](https://github.com/litixsoft/karma-mocha-reporter/commit/66e0454)), closes [#70](https://github.com/litixsoft/karma-mocha-reporter/issues/70) 39 | 40 | 41 | 42 | ## [2.0.5](https://github.com/litixsoft/karma-mocha-reporter/compare/v2.0.4...v2.0.5) (2016-07-28) 43 | 44 | 45 | ### Bug Fixes 46 | 47 | * allows disabling the divider by setting the divider option to '' or false ([25cbe87](https://github.com/litixsoft/karma-mocha-reporter/commit/25cbe87)), closes [#68](https://github.com/litixsoft/karma-mocha-reporter/issues/68) 48 | 49 | 50 | 51 | ## [2.0.4](https://github.com/litixsoft/karma-mocha-reporter/compare/v2.0.3...v2.0.4) (2016-06-10) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * no result output when no browsers are defined in the config ([53e7d65](https://github.com/litixsoft/karma-mocha-reporter/commit/53e7d65)), closes [#53](https://github.com/litixsoft/karma-mocha-reporter/issues/53) 57 | 58 | 59 | ### v2.0.3 60 | * Fix multiline string diffs 61 | 62 | ### v2.0.2 63 | * Print a test suite with it's child items only after all child items are completed 64 | 65 | ### v2.0.1 66 | * Print correct failure summary and colors when a test fails only in one browser 67 | 68 | ### v2.0.0 69 | * Move module karma to peerDependencies 70 | 71 | ### v1.3.0 72 | * Wait before printing output of a test after all browser have run the test 73 | 74 | ### v1.2.3 75 | * Set property success to `true` when a test is skipped. Prevents wrong output in the failure summary 76 | 77 | ### v1.2.2 78 | * Update error message when diff output is enabled and the required modules are missing 79 | 80 | ### v1.2.1 81 | * Check if property `assertionErrors` has at least one item before calculating the diff output 82 | 83 | ### v1.2.0 84 | * Add support for diff output for failed tests 85 | 86 | ### v1.1.6 87 | * Fix error that reporter output was truncated when running multiple browsers 88 | * Reverts part of the fix from v1.1.4 (identical it blocks within the same describe block are only printed correctly when the test are run in one browser) 89 | 90 | ### v1.1.5 91 | * Show error message when the karma runner ends with an error 92 | 93 | ### v1.1.4 94 | * Print specs correctly when names of it blocks are identical within the same describe block 95 | 96 | ### v1.1.3 97 | * Fix for divider is always "=" even the user set divider in config 98 | 99 | ### v1.1.2 100 | * Show a divider line between multiple test runs for clarity 101 | 102 | ### v1.1.1 103 | * Use overwritten colors also for the log symbols 104 | 105 | ### v1.1.0 106 | * Add option `colors` to config that allows to overwrite the default colors 107 | 108 | ### v1.0.4 109 | * Added plural or singular noun for 'test' based on count 110 | 111 | ### v1.0.3 112 | * Changed some formatting to not start at newline 113 | 114 | ### v1.0.2 115 | * enable colors when karma is piped 116 | 117 | ### v1.0.1 118 | * print out all errors in the summary when spec fails 119 | 120 | ### v1.0.0 121 | * add output option `noFailures` - when set, the failure details are not logged 122 | * time to get final with 1.0.0 :-) 123 | 124 | ### v0.3.2 125 | * strip color from symbols when colors is set to false 126 | 127 | ### v0.3.1 128 | * add option "ignoreSkipped" to ignore the skipped test in the output 129 | 130 | ### v0.3.0 131 | * add option "output" to set the output level of the reporter 132 | 133 | ### v0.2.8 134 | * add module log-symbols for printing symbols to the console 135 | 136 | ### v0.2.7 137 | * report totalTime and netTime the same way "dots" and "progress" reporters do 138 | 139 | ### v0.2.6 140 | * don't crash when the name of the describe or it block is a reserved object property (e.g. constructor, toString) 141 | 142 | ### v0.2.5 143 | * results summary is now also printed when all tests fail 144 | 145 | ### v0.2.4 146 | * better browser names formatting 147 | * fix calculating describe items' success 148 | * use karma's error formatter 149 | 150 | ### v0.2.3 151 | * fix missing test results when singleRun = true 152 | 153 | ### v0.2.2 154 | * fix that skipped test where reported as failure 155 | 156 | ### v0.2.1 157 | * make reporter compatible with karma 0.11 158 | 159 | ### v0.2.0 160 | * replace dependency color.js with chalk.js 161 | 162 | ### v0.1.0 163 | * first release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # karma-mocha-reporter 2 | 3 | > Karma reporter plugin with mocha style logging. 4 | 5 | > [![NPM version](https://badge.fury.io/js/karma-mocha-reporter.svg)](http://badge.fury.io/js/karma-mocha-reporter) 6 | [![Build Status](https://secure.travis-ci.org/litixsoft/karma-mocha-reporter.svg?branch=master)](https://travis-ci.org/litixsoft/karma-mocha-reporter) 7 | [![david-dm](https://david-dm.org/litixsoft/karma-mocha-reporter.svg?theme=shields.io)](https://david-dm.org/litixsoft/karma-mocha-reporter/) 8 | [![david-dm](https://david-dm.org/litixsoft/karma-mocha-reporter/dev-status.svg?theme=shields.io)](https://david-dm.org/litixsoft/karma-mocha-reporter#info=devDependencies&view=table) 9 | 10 | ## How does it look like 11 | ![screenshot](demo/screen.png) 12 | 13 | ## Installation 14 | The easiest way is to keep `karma-mocha-reporter` as a devDependency in your `package.json`. 15 | ```json 16 | { 17 | "devDependencies": { 18 | "karma": "^1.0.0", 19 | "karma-mocha-reporter": "^2.0.0" 20 | } 21 | } 22 | ``` 23 | 24 | You can simple do it by: 25 | 26 | $ npm install karma-mocha-reporter --save-dev 27 | 28 | ## Configuration 29 | ```js 30 | // karma.conf.js 31 | module.exports = function(config) { 32 | config.set({ 33 | frameworks: ['jasmine'], 34 | 35 | // reporters configuration 36 | reporters: ['mocha'] 37 | }); 38 | }; 39 | ``` 40 | 41 | ## Options 42 | ### colors 43 | **Type:** Object | Boolean 44 | 45 | Let's you overwrite the default colors. Possible values are all colors and background colors from [chalk](https://github.com/chalk/chalk#colors). 46 | 47 | **Possible Values:** 48 | 49 | Value | Description | Default 50 | ------ | ----------- | ------- 51 | `success` | success messages | green 52 | `info` | info messages | grey 53 | `warning` | warn messages | yellow 54 | `error` | error messages | red 55 | 56 | ```js 57 | // karma.conf.js 58 | module.exports = function(config) { 59 | config.set({ 60 | frameworks: ['jasmine'], 61 | 62 | // reporters configuration 63 | reporters: ['mocha'], 64 | 65 | // reporter options 66 | mochaReporter: { 67 | colors: { 68 | success: 'blue', 69 | info: 'bgGreen', 70 | warning: 'cyan', 71 | error: 'bgRed' 72 | }, 73 | symbols: { 74 | success: '+', 75 | info: '#', 76 | warning: '!', 77 | error: 'x' 78 | } 79 | } 80 | }); 81 | }; 82 | ``` 83 | 84 | To disable the colors please use the `colors` option in the karma config. 85 | 86 | ```js 87 | // karma.conf.js 88 | module.exports = function(config) { 89 | config.set({ 90 | frameworks: ['jasmine'], 91 | 92 | // reporters configuration 93 | reporters: ['mocha'], 94 | 95 | // disable colors 96 | colors: false 97 | }); 98 | }; 99 | ``` 100 | 101 | ### symbols 102 | **Type:** Object 103 | 104 | Let's you overwrite the default symbols. 105 | 106 | **Possible Values:** 107 | 108 | Value | Description | Default 109 | ------ | ----------- | ------- 110 | `success` | success messages | ✔ 111 | `info` | info messages | ℹ 112 | `warning` | warn messages | ⚠ 113 | `error` | error messages | ✖ 114 | 115 | ```js 116 | // karma.conf.js 117 | module.exports = function(config) { 118 | config.set({ 119 | frameworks: ['jasmine'], 120 | 121 | // reporters configuration 122 | reporters: ['mocha'], 123 | 124 | // reporter options 125 | mochaReporter: { 126 | symbols: { 127 | success: '+', 128 | info: '#', 129 | warning: '!', 130 | error: 'x' 131 | } 132 | } 133 | }); 134 | }; 135 | ``` 136 | 137 | ### output 138 | **Type:** String 139 | 140 | **Possible Values:** 141 | 142 | Value | Description 143 | ------ | ----------- 144 | `full` (default) | all output is printed to the console 145 | `autowatch` | first run will have the full output and the next runs just output the summary and errors in mocha style 146 | `minimal` | only the summary and errors are printed to the console in mocha style 147 | `noFailures` | the failure details are not logged 148 | 149 | ```js 150 | // karma.conf.js 151 | module.exports = function(config) { 152 | config.set({ 153 | frameworks: ['jasmine'], 154 | 155 | // reporters configuration 156 | reporters: ['mocha'], 157 | 158 | // reporter options 159 | mochaReporter: { 160 | output: 'autowatch' 161 | } 162 | }); 163 | }; 164 | ``` 165 | 166 | ### showDiff 167 | **Type:** String | Boolean 168 | 169 | Shows a diff output. Is disabled by default. All credits to the contributors of [mocha](https://github.com/mochajs/mocha), since the diff logic is used from there and customized for this module. 170 | 171 | ![screenshot](demo/diff.png) 172 | 173 | Currently only works with karma-mocha >= v0.2.2 Not supported for karma-jasmine since the additional properties needed to render the diff are not supported in jasmine yet. 174 | 175 | **Possible Values:** 176 | 177 | Value | Description 178 | ------ | ----------- 179 | `true` | prints each diff in its own line, same as `'unified'` 180 | `'unified'` | prints each diff in its own line 181 | `'inline'` | prints diffs inline 182 | 183 | ```js 184 | // karma.conf.js 185 | module.exports = function(config) { 186 | config.set({ 187 | frameworks: ['mocha', 'chai'], 188 | 189 | // reporters configuration 190 | reporters: ['mocha'], 191 | 192 | // reporter options 193 | mochaReporter: { 194 | showDiff: true 195 | } 196 | }); 197 | }; 198 | ``` 199 | 200 | ### divider 201 | **Type:** String 202 | 203 | **Default:** 80 equals signs ('=') 204 | 205 | The string to output between multiple test runs. Set to `false` or empty string to disable 206 | 207 | ```js 208 | // karma.conf.js 209 | module.exports = function(config) { 210 | config.set({ 211 | frameworks: ['jasmine'], 212 | 213 | // reporters configuration 214 | reporters: ['mocha'], 215 | 216 | // reporter options 217 | mochaReporter: { 218 | divider: '' 219 | } 220 | }); 221 | }; 222 | ``` 223 | 224 | ### ignoreSkipped 225 | **Type:** Boolean 226 | 227 | **Possible Values:** 228 | * `false` (default) 229 | * `true` 230 | 231 | When setting the ignoreSkipped flag to true, the reporter will ignore the skipped tests in the output and you will see 232 | only the tests that where really executed. The summary will still contain the number of skipped tests. 233 | 234 | ### maxLogLines 235 | **Type:** Number 236 | 237 | Let's you set the maximum number of lines which are printed for a failure. The default value is 999. Helps to cut long stack traces. 238 | Set the value to `-1` to disable stack traces. 239 | 240 | ### printFirstSuccess 241 | **Type:** Boolean 242 | 243 | **Possible Values:** 244 | * `false` (default) 245 | * `true` 246 | 247 | Prints the result of an it block after it is run in one browser. This options is useful when you have tests which are conditionally run in one browser only. 248 | Otherwise the result of the it block would not be printed because it was not run in all browsers. 249 | 250 | ```js 251 | // testfile.spec.js 252 | if (navigator.userAgent.match(/firefox/i)) { 253 | describe('Firefox tests', function() { 254 | it('this would only be reported when printFirstSuccess is true', function() { 255 | console.log('firefox test'); 256 | }); 257 | }); 258 | } 259 | 260 | describe('Other tests', function() { 261 | it('this should be always reported', function() { 262 | console.log('hello world'); 263 | }); 264 | }); 265 | ``` 266 | 267 | 268 | ## Contributing 269 | In lieu of a formal styleguide take care to maintain the existing coding style. Lint and test your code using [grunt](http://gruntjs.com/). 270 | 271 | You can preview your changes by running: 272 | 273 | $ npm run demo 274 | 275 | ## Author 276 | [Litixsoft GmbH](http://www.litixsoft.de) 277 | 278 | ## License 279 | Copyright (C) 2013-2017 Litixsoft GmbH 280 | Licensed under the MIT license. 281 | 282 | Permission is hereby granted, free of charge, to any person obtaining a copy 283 | of this software and associated documentation files (the "Software"), to deal 284 | in the Software without restriction, including without limitation the rights 285 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 286 | copies of the Software, and to permit persons to whom the Software is 287 | furnished to do so, subject to the following conditions: 288 | 289 | The above copyright notice and this permission notice shall be included i 290 | all copies or substantial portions of the Software. 291 | 292 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 293 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 294 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 295 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 296 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 297 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 298 | THE SOFTWARE. 299 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | // Project configuration. 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | jshint: { 8 | options: { 9 | jshintrc: true 10 | }, 11 | test: ['Gruntfile.js', 'index.js', 'demo/**/*.js'] 12 | }, 13 | copy: { 14 | demo: { 15 | expand: true, 16 | src: ['index.js', 'symbols.js'], 17 | dest: 'node_modules/karma-mocha-reporter' 18 | } 19 | }, 20 | shell: { 21 | karma: { 22 | command: './node_modules/karma/bin/karma start demo/karma.conf.js' 23 | } 24 | }, 25 | conventionalChangelog: { 26 | options: { 27 | changelogOpts: { 28 | preset: 'angular' 29 | } 30 | }, 31 | release: { 32 | src: 'CHANGELOG.md' 33 | } 34 | }, 35 | bump: { 36 | options: { 37 | files: ['package.json'], 38 | updateConfigs: ['pkg'], 39 | commitFiles: ['-a'], 40 | commitMessage: 'chore: Release v%VERSION%', 41 | push: false 42 | } 43 | }, 44 | karma: { 45 | demo: { 46 | configFile: 'demo/karma.conf.js' 47 | }, 48 | mocha: { 49 | configFile: 'demo/karma.mocha.conf.js' 50 | }, 51 | concurrency: { 52 | configFile: 'demo/karma.conf.js', 53 | browsers: ['Chrome', 'PhantomJS'], 54 | concurrency: 1, 55 | options: { 56 | files: ['demo/aDemo.spec.js', 'demo/demo.spec.js'] 57 | }, 58 | detectBrowsers: { 59 | enabled: false 60 | } 61 | }, 62 | fast: { 63 | configFile: 'demo/karma.conf.js', 64 | browsers: ['PhantomJS'], 65 | options: { 66 | files: ['demo/demo.spec.js'] 67 | }, 68 | detectBrowsers: { 69 | enabled: false 70 | } 71 | }, 72 | failInOneBrowser: { 73 | configFile: 'demo/karma.conf.js', 74 | browsers: ['PhantomJS', 'Chrome'], 75 | options: { 76 | files: ['demo/failInOneBrowser.spec.js'] 77 | }, 78 | detectBrowsers: { 79 | enabled: false 80 | } 81 | }, 82 | allBrowsers: { 83 | configFile: 'demo/karma.conf.js', 84 | options: { 85 | files: ['demo/demo.spec.js'] 86 | } 87 | }, 88 | singleBrowser: { 89 | configFile: 'demo/karma.conf.js', 90 | detectBrowsers: { 91 | enabled: false 92 | } 93 | }, 94 | success: { 95 | configFile: 'demo/karma.conf.js', 96 | browsers: ['PhantomJS', 'Firefox'], 97 | options: { 98 | files: ['demo/demo.spec.js'] 99 | }, 100 | detectBrowsers: { 101 | enabled: false 102 | } 103 | }, 104 | duplicate: { 105 | configFile: 'demo/karma.conf.js', 106 | browsers: ['PhantomJS'], 107 | options: { 108 | files: ['demo/duplicate.spec.js'] 109 | }, 110 | detectBrowsers: { 111 | enabled: false 112 | } 113 | }, 114 | reload: { 115 | configFile: 'demo/karma.conf.js', 116 | browsers: ['PhantomJS'], 117 | options: { 118 | files: ['demo/fullPageReload.spec.js'] 119 | }, 120 | detectBrowsers: { 121 | enabled: false 122 | } 123 | }, 124 | noColors: { 125 | configFile: 'demo/karma.conf.js', 126 | colors: false, 127 | detectBrowsers: { 128 | enabled: false 129 | } 130 | }, 131 | short: { 132 | configFile: 'demo/karma.conf.js', 133 | options: { 134 | files: ['demo/aDemo.spec.js'] 135 | }, 136 | detectBrowsers: { 137 | enabled: false 138 | } 139 | }, 140 | singleRun: { 141 | configFile: 'demo/karma.conf.js', 142 | singleRun: false, 143 | autoWatch: true, 144 | detectBrowsers: { 145 | enabled: false 146 | } 147 | }, 148 | fail: { 149 | configFile: 'demo/karma.conf.js', 150 | options: { 151 | files: ['demo/fail.spec.js'] 152 | }, 153 | mochaReporter: { 154 | maxLogLines: 2 155 | }, 156 | detectBrowsers: { 157 | enabled: false 158 | } 159 | }, 160 | failWithAllBrowsers: { 161 | configFile: 'demo/karma.conf.js', 162 | options: { 163 | files: ['demo/fail.spec.js'] 164 | } 165 | }, 166 | printNoFailures: { 167 | configFile: 'demo/karma.conf.js', 168 | mochaReporter: { 169 | output: 'noFailures' 170 | }, 171 | options: { 172 | files: ['demo/fail.spec.js'] 173 | }, 174 | detectBrowsers: { 175 | enabled: false 176 | } 177 | }, 178 | autowatch: { 179 | configFile: 'demo/karma.autowatch.conf.js', 180 | options: { 181 | files: ['demo/aDemo.spec.js'] 182 | }, 183 | detectBrowsers: { 184 | enabled: false 185 | } 186 | }, 187 | minimal: { 188 | configFile: 'demo/karma.autowatch.conf.js', 189 | options: { 190 | files: ['demo/aDemo.spec.js'], 191 | mochaReporter: { 192 | output: 'minimal' 193 | } 194 | }, 195 | detectBrowsers: { 196 | enabled: false 197 | } 198 | }, 199 | ignoreSkipped: { 200 | configFile: 'demo/karma.conf.js', 201 | options: { 202 | files: ['demo/skipping.spec.js'], 203 | mochaReporter: { 204 | ignoreSkipped: true 205 | } 206 | } 207 | }, 208 | colors: { 209 | configFile: 'demo/karma.conf.js', 210 | browsers: ['PhantomJS'], 211 | options: { 212 | files: ['demo/demo2.spec.js'], 213 | mochaReporter: { 214 | colors: { 215 | success: 'bgGreen', 216 | info: 'bgCyan', 217 | warning: 'wayne', 218 | error: 'bgRed' 219 | } 220 | } 221 | }, 222 | detectBrowsers: { 223 | enabled: false 224 | } 225 | }, 226 | symbols: { 227 | configFile: 'demo/karma.conf.js', 228 | browsers: ['PhantomJS'], 229 | options: { 230 | files: ['demo/demo2.spec.js'], 231 | mochaReporter: { 232 | symbols: { 233 | success: '+', 234 | info: '#', 235 | warning: '!', 236 | error: 'x' 237 | } 238 | } 239 | }, 240 | detectBrowsers: { 241 | enabled: false 242 | } 243 | }, 244 | firstSuccess: { 245 | configFile: 'demo/karma.conf.js', 246 | browsers: ['PhantomJS', 'Firefox'], 247 | options: { 248 | files: ['demo/firstSuccess.spec.js'], 249 | mochaReporter: { 250 | printFirstSuccess: true 251 | } 252 | }, 253 | detectBrowsers: { 254 | enabled: false 255 | } 256 | }, 257 | } 258 | }); 259 | 260 | // Load tasks. 261 | require('load-grunt-tasks')(grunt); 262 | 263 | grunt.registerTask('release', 'Bump version, update changelog and tag version', function (version) { 264 | grunt.task.run([ 265 | 'bump:' + (version || 'patch') + ':bump-only', 266 | 'conventionalChangelog:release', 267 | 'bump-commit' 268 | ]); 269 | }); 270 | 271 | // Register tasks. 272 | grunt.registerTask('test', ['copy:demo', 'jshint', 'karma:success']); 273 | grunt.registerTask('concurrency', ['copy:demo', 'jshint', 'karma:concurrency']); 274 | grunt.registerTask('fast', ['copy:demo', 'karma:fast']); 275 | grunt.registerTask('firstSuccess', ['copy:demo', 'karma:firstSuccess']); 276 | grunt.registerTask('short', ['copy:demo', 'karma:short']); 277 | grunt.registerTask('autowatch', ['copy:demo', 'karma:autowatch']); 278 | grunt.registerTask('minimal', ['copy:demo', 'karma:minimal']); 279 | grunt.registerTask('single', ['copy:demo', 'karma:singleRun']); 280 | grunt.registerTask('fail', ['copy:demo', 'karma:fail']); 281 | grunt.registerTask('fail2', ['copy:demo', 'karma:failWithAllBrowsers']); 282 | grunt.registerTask('fail3', ['copy:demo', 'karma:failInOneBrowser']); 283 | grunt.registerTask('printNoFailures', ['copy:demo', 'karma:printNoFailures']); 284 | grunt.registerTask('noColors', ['copy:demo', 'karma:noColors']); 285 | grunt.registerTask('ignoreSkipped', ['copy:demo', 'karma:ignoreSkipped']); 286 | grunt.registerTask('piped', ['copy:demo', 'shell:karma']); 287 | grunt.registerTask('colors', ['copy:demo', 'karma:colors']); 288 | grunt.registerTask('symbols', ['copy:demo', 'karma:symbols']); 289 | grunt.registerTask('duplicate', ['copy:demo', 'karma:duplicate']); 290 | grunt.registerTask('reload', ['copy:demo', 'karma:reload']); 291 | grunt.registerTask('mocha', ['copy:demo', 'karma:mocha']); 292 | grunt.registerTask('all', ['copy:demo', 'karma:allBrowsers']); 293 | grunt.registerTask('demo', [ 294 | 'copy:demo', 295 | 'karma:singleBrowser', 296 | 'karma:demo', 297 | 'karma:success', 298 | 'karma:firstSuccess', 299 | 'karma:fail', 300 | 'karma:failInOneBrowser', 301 | 'karma:printNoFailures', 302 | 'shell:karma', 303 | 'karma:noColors', 304 | 'karma:colors', 305 | 'karma:symbols', 306 | 'karma:duplicate', 307 | 'karma:mocha', 308 | 'karma:concurrency', 309 | 'karma:reload' 310 | ]); 311 | 312 | // Default task. 313 | grunt.registerTask('default', ['test']); 314 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chalk = require('chalk'); 4 | var symbols = require('./symbols'); 5 | 6 | /** 7 | * The MochaReporter. 8 | * 9 | * @param {!object} baseReporterDecorator The karma base reporter. 10 | * @param {!Function} formatError The karma function to format an error. 11 | * @param {!object} config The karma config. 12 | * @constructor 13 | */ 14 | var MochaReporter = function (baseReporterDecorator, formatError, config) { 15 | // extend the base reporter 16 | baseReporterDecorator(this); 17 | 18 | var self = this; 19 | var firstRun = true; 20 | var isRunCompleted = false; 21 | 22 | /** 23 | * Returns the text repeated n times. 24 | * 25 | * @param {!string} text The text. 26 | * @param {!number} n The number of times the string should be repeated. 27 | * @returns {string} 28 | */ 29 | function repeatString(text, n) { 30 | var res = []; 31 | var i; 32 | 33 | for (i = 0; i < n; i++) { 34 | res.push(text); 35 | } 36 | 37 | return res.join(''); 38 | } 39 | 40 | config.mochaReporter = config.mochaReporter || {}; 41 | 42 | var outputMode = config.mochaReporter.output || 'full'; 43 | var ignoreSkipped = config.mochaReporter.ignoreSkipped || false; 44 | var divider = config.mochaReporter.hasOwnProperty('divider') ? config.mochaReporter.divider : '='; 45 | divider = repeatString(divider || '', process.stdout.columns || 80); 46 | 47 | // disable chalk when colors is set to false 48 | chalk.enabled = config.colors !== false; 49 | 50 | // set color functions 51 | config.mochaReporter.colors = config.mochaReporter.colors || {}; 52 | 53 | // set symbol functions 54 | config.mochaReporter.symbols = config.mochaReporter.symbols || {}; 55 | 56 | // set diff output 57 | config.mochaReporter.showDiff = config.mochaReporter.showDiff || false; 58 | 59 | // print first successful result 60 | config.mochaReporter.printFirstSuccess = config.mochaReporter.printFirstSuccess || false; 61 | 62 | var colors = { 63 | success: { 64 | symbol: config.mochaReporter.symbols.success || symbols.success, 65 | print: chalk[config.mochaReporter.colors.success] || chalk.green 66 | }, 67 | info: { 68 | symbol: config.mochaReporter.symbols.info || symbols.info, 69 | print: chalk[config.mochaReporter.colors.info] || chalk.grey 70 | }, 71 | warning: { 72 | symbol: config.mochaReporter.symbols.warning || symbols.warning, 73 | print: chalk[config.mochaReporter.colors.warning] || chalk.yellow 74 | }, 75 | error: { 76 | symbol: config.mochaReporter.symbols.error || symbols.error, 77 | print: chalk[config.mochaReporter.colors.error] || chalk.red 78 | } 79 | }; 80 | 81 | // init max number of log lines 82 | config.mochaReporter.maxLogLines = config.mochaReporter.maxLogLines || 999; 83 | 84 | if (isNaN(config.mochaReporter.maxLogLines)) { 85 | self.write(colors.warning.print('Option "config.mochaReporter.maxLogLines" must be of type number. Default value 999 is used!')); 86 | config.mochaReporter.maxLogLines = 999; 87 | } 88 | 89 | // check if mocha is installed when showDiff is enabled 90 | if (config.mochaReporter.showDiff) { 91 | try { 92 | var mocha = require('mocha'); 93 | var diff = require('diff'); 94 | } catch (e) { 95 | self.write(colors.error.print('Error loading module mocha!\nYou have enabled diff output. That only works with karma-mocha and mocha installed!\nRun the following command in your command line:\n npm install karma-mocha mocha diff\n')); 96 | return; 97 | } 98 | } 99 | 100 | function getLogSymbol(color) { 101 | return chalk.enabled ? color.print(color.symbol) : chalk.stripColor(color.symbol); 102 | } 103 | 104 | /** 105 | * Returns a unified diff between two strings. 106 | * 107 | * @param {Error} err with actual/expected 108 | * @return {string} The diff. 109 | */ 110 | function unifiedDiff(err) { 111 | var indent = ' '; 112 | 113 | function cleanUp(line) { 114 | if (line[0] === '+') { 115 | return indent + colors.success.print(line); 116 | } 117 | if (line[0] === '-') { 118 | return indent + colors.error.print(line); 119 | } 120 | if (line.match(/\@\@/)) { 121 | return null; 122 | } 123 | if (line.match(/\\ No newline/)) { 124 | return null; 125 | } 126 | return indent + line; 127 | } 128 | 129 | function notBlank(line) { 130 | return line !== null; 131 | } 132 | 133 | var msg = diff.createPatch('string', err.actual, err.expected); 134 | var lines = msg.split('\n').splice(4); 135 | return '\n ' + 136 | colors.success.print('+ expected') + ' ' + 137 | colors.error.print('- actual') + 138 | '\n\n' + 139 | lines.map(cleanUp).filter(notBlank).join('\n'); 140 | } 141 | 142 | /** 143 | * Return a character diff for `err`. 144 | * 145 | * @param {Error} err 146 | * @param {string} type 147 | * @return {string} 148 | */ 149 | function errorDiff(err, type) { 150 | var actual = err.actual; 151 | var expected = err.expected; 152 | return diff['diff' + type](actual, expected).map(function (str) { 153 | if (str.added) { 154 | return colors.success.print(str.value); 155 | } 156 | if (str.removed) { 157 | return colors.error.print(str.value); 158 | } 159 | return str.value; 160 | }).join(''); 161 | } 162 | 163 | /** 164 | * Pad the given `str` to `len`. 165 | * 166 | * @param {string} str 167 | * @param {string} len 168 | * @return {string} 169 | */ 170 | function pad(str, len) { 171 | str = String(str); 172 | return Array(len - str.length + 1).join(' ') + str; 173 | } 174 | 175 | /** 176 | * Returns an inline diff between 2 strings with coloured ANSI output 177 | * 178 | * @param {Error} err with actual/expected 179 | * @return {string} Diff 180 | */ 181 | function inlineDiff(err) { 182 | var msg = errorDiff(err, 'WordsWithSpace'); 183 | 184 | // linenos 185 | var lines = msg.split('\n'); 186 | if (lines.length > 4) { 187 | var width = String(lines.length).length; 188 | msg = lines.map(function (str, i) { 189 | return pad(++i, width) + ' |' + ' ' + str; 190 | }).join('\n'); 191 | } 192 | 193 | // legend 194 | msg = '\n' + 195 | colors.success.print('expected') + 196 | ' ' + 197 | colors.error.print('actual') + 198 | '\n\n' + 199 | msg + 200 | '\n'; 201 | 202 | // indent 203 | msg = msg.replace(/^/gm, ' '); 204 | return msg; 205 | } 206 | 207 | /** 208 | * Returns a formatted time interval 209 | * 210 | * @param {!number} time The time. 211 | * @returns {string} 212 | */ 213 | function formatTimeInterval(time) { 214 | var mins = Math.floor(time / 60000); 215 | var secs = (time - mins * 60000) / 1000; 216 | var str = secs + (secs === 1 ? ' sec' : ' secs'); 217 | 218 | if (mins) { 219 | str = mins + (mins === 1 ? ' min ' : ' mins ') + str; 220 | } 221 | 222 | return str; 223 | } 224 | 225 | /** 226 | * Checks if all items are completed 227 | * 228 | * @param {object} items The item objects 229 | * @returns {boolean} 230 | */ 231 | function allChildItemsAreCompleted(items) { 232 | var item; 233 | var isCompleted = true; 234 | 235 | Object.keys(items).forEach(function (key) { 236 | item = items[key]; 237 | 238 | if (item.type === 'it') { 239 | isCompleted = isCompleted && item.isCompleted; 240 | } else if (item.items) { 241 | // recursive check of child items 242 | isCompleted = isCompleted && allChildItemsAreCompleted(item.items); 243 | } 244 | }); 245 | 246 | return isCompleted; 247 | } 248 | 249 | /** 250 | * Prints a single item 251 | * 252 | * @param {!object} item The item to print 253 | * @param {number} depth The depth 254 | */ 255 | function printItem(item, depth) { 256 | // only print to output once 257 | if (item.name && !item.printed && (!item.skipped || !ignoreSkipped)) { 258 | // only print it block when it was ran through all browsers 259 | if (item.type === 'it' && !item.isCompleted) { 260 | return; 261 | } 262 | 263 | // indent 264 | var line = repeatString(' ', depth) + item.name; 265 | 266 | // it block 267 | if (item.type === 'it') { 268 | if (item.skipped) { 269 | // print skipped tests info 270 | line = colors.info.print(chalk.stripColor(line) + ' (skipped)'); 271 | } else { 272 | // set color to success or error 273 | line = item.success ? colors.success.print(line) : colors.error.print(line); 274 | } 275 | } else { 276 | // print name of a suite block in bold 277 | line = chalk.bold(line); 278 | } 279 | 280 | // use write method of baseReporter 281 | self.write(line + '\n'); 282 | 283 | // set item as printed 284 | item.printed = true; 285 | } 286 | } 287 | 288 | /** 289 | * Writes the test results to the output 290 | * 291 | * @param {!object} suite The test suite 292 | * @param {number=} depth The indention. 293 | */ 294 | function print(suite, depth) { 295 | var keys = Object.keys(suite); 296 | var length = keys.length; 297 | var i, item; 298 | 299 | for (i = 0; i < length; i++) { 300 | item = suite[keys[i]]; 301 | 302 | // start of a new suite 303 | if (item.isRoot) { 304 | depth = 1; 305 | } 306 | 307 | if (item.items) { 308 | var allChildItemsCompleted = allChildItemsAreCompleted(item.items); 309 | 310 | if (allChildItemsCompleted) { 311 | // print current item because all children are completed 312 | printItem(item, depth); 313 | 314 | // print all child items 315 | print(item.items, depth + 1); 316 | } 317 | } else { 318 | // print current item which has no children 319 | printItem(item, depth); 320 | } 321 | } 322 | } 323 | 324 | /** 325 | * Writes the failed test to the output 326 | * 327 | * @param {!object} suite The test suite 328 | * @param {number=} depth The indention. 329 | */ 330 | function printFailures(suite, depth) { 331 | var keys = Object.keys(suite); 332 | var length = keys.length; 333 | var i, item; 334 | 335 | for (i = 0; i < length; i++) { 336 | item = suite[keys[i]]; 337 | 338 | // start of a new suite 339 | if (item.isRoot) { 340 | depth = 1; 341 | } 342 | 343 | // only print to output when test failed 344 | if (item.name && !item.success && !item.skipped) { 345 | // indent 346 | var line = repeatString(' ', depth) + item.name; 347 | 348 | // it block 349 | if (item.type === 'it') { 350 | // make item name error 351 | line = colors.error.print(line) + '\n'; 352 | 353 | // add all browser in which the test failed with color warning 354 | for (var bi = 0; bi < item.failed.length; bi++) { 355 | var browserName = item.failed[bi]; 356 | line += repeatString(' ', depth + 1) + chalk.italic(colors.warning.print(browserName)) + '\n'; 357 | } 358 | 359 | // add the error log in error color 360 | item.log = item.log || []; 361 | var log = item.log.length ? item.log[0].split('\n') : []; 362 | var linesToLog = config.mochaReporter.maxLogLines; 363 | var ii = 0; 364 | 365 | // set number of lines to output 366 | if (log.length < linesToLog) { 367 | linesToLog = log.length; 368 | } 369 | 370 | // print diff 371 | if (config.mochaReporter.showDiff && item.assertionErrors && item.assertionErrors[0]) { 372 | var errorMessage = log.splice(0, 1)[0]; 373 | 374 | // print error message before diff 375 | line += colors.error.print(repeatString(' ', depth) + errorMessage + '\n'); 376 | 377 | var expected = item.assertionErrors[0].expected; 378 | var actual = item.assertionErrors[0].actual; 379 | var utils = mocha.utils; 380 | var err = { 381 | actual: actual, 382 | expected: expected 383 | }; 384 | 385 | if (String(err.actual).match(/^".*"$/) && String(err.expected).match(/^".*"$/)) { 386 | try { 387 | err.actual = JSON.parse(err.actual); 388 | err.expected = JSON.parse(err.expected); 389 | } catch (e) { } 390 | } 391 | 392 | // ensure that actual and expected are strings 393 | if (!(utils.isString(actual) && utils.isString(expected))) { 394 | err.actual = utils.stringify(actual); 395 | err.expected = utils.stringify(expected); 396 | } 397 | 398 | // create diff 399 | var diff = config.mochaReporter.showDiff === 'inline' ? inlineDiff(err) : unifiedDiff(err); 400 | 401 | line += diff + '\n'; 402 | 403 | // print formatted stack trace after diff 404 | for (ii; ii < linesToLog; ii++) { 405 | line += colors.error.print(formatError(log[ii])); 406 | } 407 | } else { 408 | for (ii; ii < linesToLog; ii++) { 409 | line += colors.error.print(formatError(log[ii], repeatString(' ', depth))); 410 | } 411 | } 412 | } 413 | 414 | // use write method of baseReporter 415 | self.write(line + '\n'); 416 | } 417 | 418 | if (item.items) { 419 | // print all child items 420 | printFailures(item.items, depth + 1); 421 | } 422 | } 423 | } 424 | 425 | /** 426 | * Returns all properties of the given object 427 | * 428 | * @param {!Object} obj 429 | * @returns {Array} 430 | */ 431 | function listAllObjectProperties(obj) { 432 | var objectToInspect; 433 | var result = []; 434 | 435 | for (objectToInspect = obj; objectToInspect !== null; objectToInspect = Object.getPrototypeOf(objectToInspect)) { 436 | result = result.concat(Object.getOwnPropertyNames(objectToInspect)); 437 | } 438 | 439 | return result; 440 | } 441 | 442 | /** 443 | * Returns a singularized or plularized noun for "test" based on test count 444 | * 445 | * @param {!Number} testCount 446 | * @returns {String} 447 | */ 448 | function getTestNounFor(testCount) { 449 | if (testCount === 1) { 450 | return 'test'; 451 | } 452 | return 'tests'; 453 | } 454 | 455 | /** 456 | * Returns if the property is an reserverd object property 457 | * 458 | * @param {!Object} obj 459 | * @param {!String} property 460 | * @returns {boolean} 461 | */ 462 | function isReservedProperty(obj, property) { 463 | return listAllObjectProperties(Object.getPrototypeOf(obj)).indexOf(property) > -1; 464 | } 465 | 466 | /** 467 | * Called each time a test is completed in a given browser. 468 | * 469 | * @param {!object} browser The current browser. 470 | * @param {!object} result The result of the test. 471 | */ 472 | function specComplete(browser, result) { 473 | // complete path of the test 474 | var path = [].concat(result.suite, result.description); 475 | var maxDepth = path.length - 1; 476 | 477 | path.reduce(function (suite, description, depth) { 478 | if (isReservedProperty(suite, description)) { 479 | self.write(colors.warning.print('Reserved name for ' + (depth === maxDepth ? 'it' : 'describe') + ' block (' + description + ')! Please use an other name, otherwise the result are not printed correctly')); 480 | self.write('\n'); 481 | return {}; 482 | } 483 | 484 | var item; 485 | 486 | if (suite.hasOwnProperty(description) && suite[description].type === 'it' && self.numberOfBrowsers === 1) { 487 | item = {}; 488 | description += ' '; 489 | } else { 490 | item = suite[description] || {}; 491 | } 492 | 493 | suite[description] = item; 494 | 495 | item.name = description; 496 | item.isRoot = depth === 0; 497 | item.type = 'describe'; 498 | item.skipped = result.skipped; 499 | item.success = (item.success === undefined ? true : item.success) && result.success; 500 | 501 | // set item success to true when item is skipped 502 | if (item.skipped) { 503 | item.success = true; 504 | } 505 | 506 | // it block 507 | if (depth === maxDepth) { 508 | item.type = 'it'; 509 | item.count = item.count || 0; 510 | item.count++; 511 | item.failed = item.failed || []; 512 | item.success = result.success && item.success; 513 | item.name = (item.success ? getLogSymbol(colors.success) : getLogSymbol(colors.error)) + ' ' + item.name; 514 | item.skipped = result.skipped; 515 | item.visited = item.visited || []; 516 | item.visited.push(browser.name); 517 | self.netTime += result.time; 518 | 519 | if (result.skipped) { 520 | self.numberOfSkippedTests++; 521 | } 522 | 523 | if (result.success === false) { 524 | // add browser to failed browsers array 525 | item.failed.push(browser.name); 526 | 527 | // add error log 528 | item.log = result.log; 529 | 530 | // add assertion errors if available (currently in karma-mocha) 531 | item.assertionErrors = result.assertionErrors; 532 | } 533 | 534 | if (config.reportSlowerThan && result.time > config.reportSlowerThan) { 535 | // add slow report warning 536 | item.name += colors.warning.print((' (slow: ' + formatTimeInterval(result.time) + ')')); 537 | self.numberOfSlowTests++; 538 | } 539 | 540 | if (item.count === self.numberOfBrowsers || config.mochaReporter.printFirstSuccess) { 541 | item.isCompleted = true; 542 | 543 | // print results to output when test was ran through all browsers 544 | if (outputMode !== 'minimal') { 545 | print(self.allResults, depth); 546 | } 547 | } 548 | } else { 549 | item.items = item.items || {}; 550 | } 551 | 552 | return item.items; 553 | }, self.allResults); 554 | } 555 | 556 | self.specSuccess = specComplete; 557 | self.specSkipped = specComplete; 558 | self.specFailure = specComplete; 559 | 560 | self.onSpecComplete = function (browser, result) { 561 | specComplete(browser, result); 562 | }; 563 | 564 | self.onRunStart = function () { 565 | if (!firstRun && divider) { 566 | self.write('\n' + chalk.bold(divider) + '\n'); 567 | } 568 | firstRun = false; 569 | isRunCompleted = false; 570 | 571 | self.write('\n' + chalk.underline.bold('START:') + '\n'); 572 | self._browsers = []; 573 | self.allResults = {}; 574 | self.totalTime = 0; 575 | self.netTime = 0; 576 | self.numberOfSlowTests = 0; 577 | self.numberOfSkippedTests = 0; 578 | self.numberOfBrowsers = (config.browsers || []).length || 1; 579 | }; 580 | 581 | self.onBrowserStart = function (browser) { 582 | self._browsers.push(browser); 583 | }; 584 | 585 | self.onRunComplete = function (browsers, results) { 586 | browsers.forEach(function (browser) { 587 | self.totalTime += browser.lastResult.totalTime; 588 | }); 589 | 590 | // print extra error message for some special cases, e.g. when having the error "Some of your tests did a full page reload!" the onRunComplete() method is called twice 591 | if (results.error && isRunCompleted) { 592 | self.write('\n'); 593 | self.write(getLogSymbol(colors.error) + colors.error.print(' Error while running the tests! Exit code: ' + results.exitCode)); 594 | self.write('\n\n'); 595 | return; 596 | } 597 | 598 | isRunCompleted = true; 599 | 600 | self.write('\n' + colors.success.print('Finished in ' + formatTimeInterval(self.totalTime) + ' / ' + 601 | formatTimeInterval(self.netTime) + ' @ ' + new Date().toTimeString())); 602 | self.write('\n\n'); 603 | 604 | if (browsers.length > 0 && !results.disconnected) { 605 | self.write(chalk.underline.bold('SUMMARY:') + '\n'); 606 | self.write(colors.success.print(getLogSymbol(colors.success) + ' ' + results.success + ' ' + getTestNounFor(results.success) + ' completed')); 607 | self.write('\n'); 608 | 609 | if (self.numberOfSkippedTests > 0) { 610 | self.write(colors.info.print(getLogSymbol(colors.info) + ' ' + self.numberOfSkippedTests + ' ' + getTestNounFor(self.numberOfSkippedTests) + ' skipped')); 611 | self.write('\n'); 612 | } 613 | 614 | if (self.numberOfSlowTests > 0) { 615 | self.write(colors.warning.print(getLogSymbol(colors.warning) + ' ' + self.numberOfSlowTests + ' ' + getTestNounFor(self.numberOfSlowTests) + ' slow')); 616 | self.write('\n'); 617 | } 618 | 619 | if (results.failed) { 620 | self.write(colors.error.print(getLogSymbol(colors.error) + ' ' + results.failed + ' ' + getTestNounFor(results.failed) + ' failed')); 621 | self.write('\n'); 622 | 623 | if (outputMode !== 'noFailures') { 624 | self.write('\n' + chalk.underline.bold('FAILED TESTS:') + '\n'); 625 | printFailures(self.allResults); 626 | } 627 | } 628 | } 629 | 630 | if (outputMode === 'autowatch') { 631 | outputMode = 'minimal'; 632 | } 633 | }; 634 | }; 635 | 636 | // inject karma runner baseReporter and config 637 | MochaReporter.$inject = ['baseReporterDecorator', 'formatError', 'config']; 638 | 639 | // PUBLISH DI MODULE 640 | module.exports = { 641 | 'reporter:mocha': ['type', MochaReporter] 642 | }; 643 | --------------------------------------------------------------------------------