<repo_name>paolord/cappu<file_sep>/test/cappu.test.js
'use strict';

const cli = require('../lib/cli');
const cappu = require('../lib/cappu');

describe('Integration', () => {
  it('should trigger the callback when it is time to trigger growl and display results', (done) => {
    const options = cli(['node', 'cappu', 'npm', 'run', 'test:docker']);
    cappu(options, () => {
      done(); // call done to emulate triggering growl
    });
  });
});<file_sep>/package.json
{
  "name": "cappu",
  "version": "0.1.3",
  "description": "Trigger desktop notifications from JS testing frameworks",
  "main": "index.js",
  "scripts": {
    "build": "node_modules/.bin/babel src --out-dir lib",
    "pretest": "node_modules/.bin/babel src --out-dir lib",
    "postinstall": "docker build -t test/cappu .",
    "test": "NODE_ENV=test node_modules/.bin/_mocha --compilers js:babel-core/register --reporter spec --colors --timeout 60000 ./test/*.test.js",
    "test:docker": "docker run -v $(pwd):/usr/src/app -w /usr/src/app test/cappu",
    "test:demo": "NODE_ENV=test node_modules/.bin/_mocha --compilers js:babel-core/register --reporter spec --colors --timeout 10000 ./test/demo/test.js"
  },
  "bin": {
    "cappu": "./bin/cappu.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/paolord/cappu.git"
  },
  "author": "<NAME> <<EMAIL>>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/paolord/cappu/issues"
  },
  "homepage": "https://github.com/paolord/cappu#readme",
  "devDependencies": {
    "babel-cli": "^6.16.0",
    "babel-core": "^6.17.0",
    "babel-preset-es2015": "^6.16.0",
    "babel-preset-stage-0": "^6.16.0",
    "chai": "^3.5.0",
    "eslint": "^3.7.1",
    "mocha": "^3.1.0"
  },
  "dependencies": {
    "growl": "^1.9.2"
  }
}
<file_sep>/test/demo/test.js
'use strict';
const expect = require('chai').expect;
describe('demo only', () => {
  it('should pass', () => {
    expect(1).to.eql(1);
  });
});<file_sep>/lib/parser.js
'use strict';

var detected = false;
var code = 0;

/* mocha spec reporter */
var passing = void 0;
var failing = void 0;
var pending = void 0;

/*  mocha tap reporter */
var pass = void 0;
var fail = void 0;
var tests = void 0;

function returnIfNotEmpty(list, index, d) {
  if (typeof list === 'undefined' || list === null) {
    return '';
  } else if (index < list.length) {
    if (typeof list[index] !== 'undefined' && list[index] !== null) {
      return list[index];
    }
  }
  if (typeof d !== 'undefined') {
    return d;
  }
  return '';
}

module.exports = function (data, cb) {
  if (!detected) {
    if (data.indexOf('mocha') !== -1) {
      var reporter = data.match(/(--reporter \w+)/i)[0] || '';
      detected = true;
      code = 0;
      if (reporter.split(' ')[1] === 'spec') {
        code = 0;
      }
      if (reporter.split(' ')[1] === 'tap') {
        code = 1;
      }
    }
  }
  if (detected) {
    /* code 0: mocha spec reporter */
    if (code === 0) {
      if (data.indexOf('passing') !== -1 || data.indexOf('failing') !== -1 || data.indexOf('pending') !== -1) {
        passing = returnIfNotEmpty(data.match(/(\d+ passing)/i), 0);
        failing = returnIfNotEmpty(data.match(/(\d+ failing)/i), 0);
        pending = returnIfNotEmpty(data.match(/(\d+ pending)/i), 0);

        cb([passing, failing, pending].join(' '));
      }
    }

    /* code 1: mocha tap reporter */
    if (code === 1) {
      if (data.indexOf('# tests') !== -1 || data.indexOf('# pass') !== -1 || data.indexOf('# fail') !== -1) {
        tests = returnIfNotEmpty(data.match(/(\# tests \d+)/i), 0, '# tests 0');
        pass = returnIfNotEmpty(data.match(/(\# pass \d+)/i), 0, '# pass 0');
        fail = returnIfNotEmpty(data.match(/(\# fail \d+)/i), 0, '# fail 0');

        cb(['Test: ' + tests.split(' ')[2], 'Pass: ' + pass.split(' ')[2], 'Fail: ' + fail.split(' ')[2]].join(' '));
      }
    }
    if (process.env.NODE_ENV === 'test') {
      code = 0;
      detected = false;
    }
  }
};<file_sep>/Dockerfile
FROM node:4.4.7-slim

ENTRYPOINT [ "npm", "run", "test:demo" ]<file_sep>/README.md
# cappu

Trigger desktop notifications from JS testing frameworks, based on growl.

[![Travis](https://img.shields.io/travis/paolord/cappu.svg)](https://travis-ci.org/paolord/cappu) 
[![npm](https://img.shields.io/npm/v/cappu.svg?maxAge=2592000)](https://www.npmjs.com/package/cappu) 
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/paolord/cappu/master/LICENSE)

Specially useful when tests are ran inside a docker container.

Works with the following testing frameworks and reporters:
* Mocha
  * spec
  * tap

## Installation
Follow instructions on [node-growl](https://github.com/tj/node-growl) and install it, then install cappu:
```
$ npm install -g cappu
```

## Example Use Case
Mocha tests are ran inside a docker container and you want to trigger notifications on the host OS.

test.js
```
const expect = require('chai').expect;
describe('demo only', () => {
  it('should pass', () => {
    expect(1).to.eql(1);
  });
});
```
package.json
```
...
  "scripts": {
    "test": "docker build -t test/cappu-test . && docker run -d test/cappu-test",
    "test:mocha": "mocha --reporter spec --colors ./test/demo/test.js"
  }
...
```
Dockerfile
```
FROM node:4

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY package.json /usr/src/app/
RUN npm install

COPY . /usr/src/app

CMD [ "npm", "run", "test:mocha" ]
```
Run the test through the cappu command
```
$ cappu npm test
```

## Tests
Install docker before running:
```
$ npm test
```<file_sep>/test/parser.test.js
'use strict';

const expect = require('chai').expect;

const parser = require('../lib/parser');

describe('Parser', () => {
  describe('#mocha', () => {
    it('should detect the correct result from spec reporter', () => {
      // emulate mocha stdout
      const data = 'mocha --reporter spec ./test/*.unit.js\
                    25 passing (11s)\
                    2 pending';
      parser(data, (results) => {
        expect(results).to.eql('25 passing  2 pending');
      });
    });
    it('should detect the correct result from tap reporter', () => {
      // emulate mocha stdout
      const data = 'mocha --reporter tap ./test/*.unit.js\
                    # tests 25\
                    # pass 25\
                    # fail 0';
      parser(data, (results) => {
        expect(results).to.eql('Test: 25 Pass: 25 Fail: 0');
      });
    });
  });
});<file_sep>/src/cli.js
'use strict';

module.exports = function(argv) {
  const cmd = argv[2];
  const args = argv.splice(3, argv.length);

  return {
    cmd,
    args,
  };
};<file_sep>/src/cappu.js
'use strict';

const spawn = require('child_process').spawn;
const growl = require('growl');
const parser = require('./parser');

module.exports = (options, cb) => {
  const main = options.cmd;
  const args = options.args;
  const logging = options.logging || true;

  const cmd = spawn(main, args);

  cmd.stdout.on('data', (out) => {
    const data = `${out}`;
    if (logging) {
      console.log(data);
    }
    console.log(data);
    parser(data, (results) => {
      if (process.env.NODE_ENV !== 'test') {
        growl(`Test Complete:\n${results}`);
      } else {
        cb();
      }
    });
  });

  // cmd.stderr.on('data', (out) => {
  //   // outputLines.push(data);
  // });

  cmd.on('close', (code) => {
    console.log(`Exited with code ${code}`);
  });
};<file_sep>/test/cli.test.js
'use strict';

const expect = require('chai').expect;

const cli = require('../lib/cli');

describe('CLI', () => {
  it('should return the correct arguments', () => {
    // include node command at the start because of #!/usr/bin/env node
    const options = cli(['node', 'cappu', 'npm', 'test']);
    
    expect(options).to.deep.eql({
      cmd: 'npm',
      args: ['test'],
    });
  });
});<file_sep>/src/index.js
'use strict';

const cappu = require('./cappu');

module.exports = cappu;<file_sep>/lib/cappu.js
'use strict';

var spawn = require('child_process').spawn;
var growl = require('growl');
var parser = require('./parser');

module.exports = function (options, cb) {
  var main = options.cmd;
  var args = options.args;
  var logging = options.logging || true;

  var cmd = spawn(main, args);

  cmd.stdout.on('data', function (out) {
    var data = '' + out;
    if (logging) {
      console.log(data);
    }
    console.log(data);
    parser(data, function (results) {
      if (process.env.NODE_ENV !== 'test') {
        growl('Test Complete:\n' + results);
      } else {
        cb();
      }
    });
  });

  // cmd.stderr.on('data', (out) => {
  //   // outputLines.push(data);
  // });

  cmd.on('close', function (code) {
    console.log('Exited with code ' + code);
  });
};<file_sep>/src/parser.js
'use strict';

let detected = false;
let code = 0;

/* mocha spec reporter */
let passing;
let failing;
let pending;

/*  mocha tap reporter */
let pass;
let fail;
let tests;

function returnIfNotEmpty(list, index, d) {
  if (typeof list === 'undefined' || list === null) {
    return '';
  } else if (index < list.length) {
    if (typeof list[index] !== 'undefined' && list[index] !== null) {
      return list[index];
    }
  }
  if (typeof d !== 'undefined') {
    return d;
  }
  return '';
}

module.exports = (data, cb) => {
  if (!detected) {
    if (data.indexOf('mocha') !== -1) {
      const reporter = data.match(/(--reporter \w+)/i)[0] || '';
      detected = true;
      code = 0;
      if (reporter.split(' ')[1] === 'spec') {
        code = 0;
      }
      if (reporter.split(' ')[1] === 'tap') {
        code = 1;
      }
    }
  }
  if (detected) {
    /* code 0: mocha spec reporter */
    if (code === 0) {
      if (data.indexOf('passing') !== -1 ||
          data.indexOf('failing') !== -1 ||
          data.indexOf('pending') !== -1) {
        passing = returnIfNotEmpty(data.match(/(\d+ passing)/i), 0);
        failing = returnIfNotEmpty(data.match(/(\d+ failing)/i), 0);
        pending = returnIfNotEmpty(data.match(/(\d+ pending)/i), 0);

        cb([
          passing,
          failing,
          pending,
        ].join(' '));
      }
    }

    /* code 1: mocha tap reporter */
    if (code === 1) {
      if (data.indexOf('# tests') !== -1 ||
          data.indexOf('# pass') !== -1 ||
          data.indexOf('# fail') !== -1) {
        tests = returnIfNotEmpty(data.match(/(\# tests \d+)/i), 0, '# tests 0');
        pass = returnIfNotEmpty(data.match(/(\# pass \d+)/i), 0, '# pass 0');
        fail = returnIfNotEmpty(data.match(/(\# fail \d+)/i), 0, '# fail 0');
        
        cb([
          `Test: ${tests.split(' ')[2]}`,
          `Pass: ${pass.split(' ')[2]}`,
          `Fail: ${fail.split(' ')[2]}`,
        ].join(' '));
      }
    }
    if (process.env.NODE_ENV === 'test') {
      code = 0;
      detected = false;
    }
  }
};