UNPKG

chimpy

Version:

Develop acceptance tests & end-to-end tests with realtime feedback.

814 lines (696 loc) 26.4 kB
jest.dontMock('../lib/chimp.js'); jest.dontMock('../lib/boolean-helper'); jest.dontMock('underscore'); jest.dontMock('async'); jest.dontMock('wrappy'); jest.dontMock('../lib/cucumberjs/cucumber.js'); beforeEach(() => { jest.resetModules(); }); describe('Chimp', () => { jest.genMockFromModule('chromedriver'); jest.genMockFromModule('fs-extra'); const Chimp = require('../lib/chimp'); describe('bin path', () => { it('sets the bin path to the location of chimp', () => { expect(Chimp.bin.match(/bin\/chimp$/)).not.toBe(null); }); }); describe('init', () => { it('calls selectMode right away if it does not find package.json', () => { const chimp = new Chimp(); const restore = chimp.fs.existsSync; chimp.fs.existsSync = jest.fn().mockReturnValue(false); chimp.informUser = jest.fn(); chimp.exec = jest.fn(); chimp.selectMode = jest.fn(); const callback = function () {}; chimp.init(callback); expect(chimp.selectMode).toBeCalledWith(callback); expect(chimp.exec).not.toBeCalled(); chimp.fs.existsSync = restore; }); it('does not executes npm install if the offline option is set', () => { const chimp = new Chimp({ offline: true }); const restore = chimp.fs.existsSync; chimp.fs.existsSync = jest.fn().mockReturnValue(true); chimp.informUser = jest.fn(); chimp.exec = jest.fn(); chimp.selectMode = jest.fn(); const callback = jest.fn(); chimp.init(callback); expect(chimp.exec.mock.calls.length).toBe(0); expect(chimp.selectMode.mock.calls.length).toBe(1); expect(callback.mock.calls.length).toBe(0); chimp.fs.existsSync = restore; }); it('executes npm install then calls selectMode when there are no errors', () => { const chimp = new Chimp(); const restore = chimp.fs.existsSync; chimp.fs.existsSync = jest.fn().mockReturnValue(true); chimp.informUser = jest.fn(); chimp.exec = jest.fn().mockImplementation((cmd, callback) => callback(null)); chimp.selectMode = jest.fn(); const callback = function () {}; chimp.init(callback); expect(chimp.selectMode).toBeCalledWith(callback); chimp.fs.existsSync = restore; }); }); describe('selectMode', () => { it('runs in single mode when no mode option is passed', () => { const chimp = new Chimp(); const mockedWatch = jest.spyOn(chimp, "watch"); const mockedRun = jest.spyOn(chimp, "run"); const mockedServer = jest.spyOn(chimp, "server"); const callback = function () {}; chimp.selectMode(callback); expect(mockedRun.mock.calls.length).toBe(1); expect(mockedRun.mock.calls[0][0]).toBe(callback); expect(mockedWatch.mock.calls.length).toBe(0); expect(mockedServer.mock.calls.length).toBe(0); mockedWatch.mockRestore(); mockedRun.mockRestore(); mockedServer.mockRestore(); }); it('runs in watch mode)', () => { const chimp = new Chimp({ watch: true }); const mockedWatch = jest.spyOn(chimp, "watch"); const mockedRun = jest.spyOn(chimp, "run"); const mockedServer = jest.spyOn(chimp, "server"); chimp.selectMode(); expect(mockedWatch.mock.calls.length).toBe(1); expect(mockedWatch.mock.calls[0].length).toBe(0); expect(mockedRun.mock.calls.length).toBe(0); expect(mockedServer.mock.calls.length).toBe(0); mockedWatch.mockRestore(); mockedRun.mockRestore(); mockedServer.mockRestore(); }); it('runs in server mode)', () => { const chimp = new Chimp({ server: true }); const mockedWatch = jest.spyOn(chimp, "watch"); const mockedRun = jest.spyOn(chimp, "run"); const mockedServer = jest.spyOn(chimp, "server"); chimp.selectMode(); expect(mockedServer.mock.calls.length).toBe(1); expect(typeof mockedServer.mock.calls[0][0]).toBe('undefined'); expect(mockedRun.mock.calls.length).toBe(0); expect(mockedWatch.mock.calls.length).toBe(0); mockedWatch.mockRestore(); mockedRun.mockRestore(); mockedServer.mockRestore(); }); }); describe('watch', () => { it('initializes chokidar', () => { jest.doMock('chokidar', () => { return { watch: jest.fn(), watcher: { on: jest.fn(), once: jest.fn() } }; }); const chokidar = require('chokidar'); chokidar.watch.mockReturnValue(chokidar.watcher); const Chimp = require('../lib/chimp.js'); const options = { path: 'abc' }; const chimp = new Chimp(options); chimp.run = jest.fn(); chimp.watch(); expect(chokidar.watch.mock.calls.length).toEqual(1); expect(chokidar.watch.mock.calls[0][0]).toEqual([options.path]); }); it('all listener is registered after watcher is ready', () => { jest.doMock('chokidar', () => { return { watch: jest.fn(), watcher: { on: jest.fn(), once: jest.fn() } }; }); const chokidar = require('chokidar'); chokidar.watch.mockReturnValue(chokidar.watcher); const Chimp = require('../lib/chimp.js'); const options = { path: 'abc' }; const chimp = new Chimp(options); chimp.run = jest.fn(); chokidar.watcher.on = jest.fn(); chokidar.watcher.once = jest.fn(); chimp.watch(); expect(chokidar.watcher.once.mock.calls[0][0]).toBe('ready'); const readyCallback = chokidar.watcher.once.mock.calls[0][1]; readyCallback(); expect(chokidar.watcher.on.mock.calls[0][0]).toBe('all'); }); it('an non-unlink event triggers the interrupt and run sequence', function () { jest.doMock('chokidar', () => { return { watch: jest.fn(), watcher: { on: jest.fn(), once: jest.fn() } }; }); const chokidar = require('chokidar'); chokidar.watch.mockReturnValue(chokidar.watcher); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp(); chimp.run = jest.fn(); const self = this; self.func = null; self.timeout = null; chimp._getDebouncedFunction = function (func, timeout) { self.allCallback = func; self.timeout = timeout; }; chimp.watch(); const readyCallback = chokidar.watcher.once.mock.calls[0][1]; readyCallback(); chimp.rerun = jest.fn(); self.allCallback('not-unlink'); expect(chimp.rerun.mock.calls.length).toBe(1); }); it('a deleted feature does not trigger the interrupt and run sequence', () => { jest.doMock('chokidar', () => { return { watch: jest.fn(), watcher: { on: jest.fn(), once: jest.fn() } }; }); const chokidar = require('chokidar'); chokidar.watch.mockReturnValue(chokidar.watcher); const Chimp = require('../lib/chimp.js'); // var _on = process.on; // process.on = jest.fn(); const chimp = new Chimp(); chimp.run = jest.fn(); chimp.watch(); const readyCallback = chokidar.watcher.once.mock.calls[0][1]; readyCallback(); const allCallback = chokidar.watcher.on.mock.calls[0][1]; chimp.rerun = jest.fn(); allCallback('unlink', '/path/some.feature'); expect(chimp.rerun.mock.calls.length).toBe(0); // process.on = _on; }); it('a deleted non-feature triggers the interrupt and run sequence', function () { jest.doMock('chokidar', () => { return { watch: jest.fn(), watcher: { on: jest.fn(), once: jest.fn() } }; }); const chokidar = require('chokidar'); chokidar.watch.mockReturnValue(chokidar.watcher); const async = require('async'); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp(); chimp.run = jest.fn(); const self = this; self.func = null; self.timeout = null; chimp._getDebouncedFunction = function (func, timeout) { self.allCallback = func; self.timeout = timeout; }; chimp.watch(); const readyCallback = chokidar.watcher.once.mock.calls[0][1]; readyCallback(); chimp.rerun = jest.fn(); this.allCallback('unlink', '/path/some.feature.js'); expect(chimp.rerun.mock.calls.length).toBe(1); }); it('runs on startup', () => { jest.doMock('chokidar', () => { return { watch: jest.fn(), watcher: { on: jest.fn(), once: jest.fn() } }; }); const chokidar = require('chokidar'); chokidar.watch.mockReturnValue(chokidar.watcher); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp(); chimp.run = jest.fn(); chimp.watch(); const readyCallback = chokidar.watcher.once.mock.calls[0][1]; readyCallback(); expect(chimp.run.mock.calls.length).toBe(1); }); it('uses the watchTag with cucumber', () => { jest.doMock('chokidar', () => { return { watch: jest.fn(), watcher: { on: jest.fn(), once: jest.fn() } }; }); const chokidar = require('chokidar'); chokidar.watch.mockReturnValue(chokidar.watcher); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp({ watchTags: '@someTag,@andAnotherTag' }); chimp.watch(); expect(chimp.options.tags).toBe('@someTag,@andAnotherTag'); }); }); describe('server', () => { it('listens on a freeport when server-port is not provided', () => { const freeport = require('freeport'); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp(); chimp.server(); expect(freeport.mock.calls.length).toBe(1); }); it('listens on the server-port when it is provided', () => { const freeport = require('freeport'); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp({ serverPort: 1234 }); chimp._startServer = jest.fn(); chimp.server(); expect(chimp._startServer.mock.calls.length).toBe(1); expect(chimp._startServer.mock.calls[0][0]).toBe(1234); expect(freeport.mock.calls.length).toBe(0); }); it('handshakes with a DDP endpoint with the server address on startup if ddp is passed', () => {// TODO having some issues testing this. DDPClient is tricky to jest up }); it('exposes the run and interrupt endpoints', () => { const Hapi = require('hapi'); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp({ serverHost: 'localhost', serverPort: 1234 }); chimp.server(); expect(Hapi.instance.route.mock.calls[0][0].method).toBe('GET'); expect(Hapi.instance.route.mock.calls[0][0].path).toBe('/run'); expect(Hapi.instance.route.mock.calls[1][0].method).toBe('GET'); expect(Hapi.instance.route.mock.calls[1][0].path).toBe('/run/{absolutePath*}'); expect(Hapi.instance.route.mock.calls[2][0].method).toBe('GET'); expect(Hapi.instance.route.mock.calls[2][0].path).toBe('/interrupt'); expect(Hapi.instance.route.mock.calls[3][0].method).toBe('GET'); expect(Hapi.instance.route.mock.calls[3][0].path).toBe('/runAll'); }); it('returns cucumber results when run handler is called successfully', () => { const Hapi = require('hapi'); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp({ serverHost: 'localhost', serverPort: 1234 }); chimp.rerun = jest.fn().mockImplementation(callback => callback(null, [null, [null, 'cucumber results']])); chimp.server(); const getHandler = Hapi.instance.route.mock.calls[0][0].handler; const headerMock = jest.fn(); const reply = jest.fn().mockReturnValue({ header: headerMock }); getHandler(null, reply); expect(reply.mock.calls[0][0]).toBe('cucumber results'); }); it('returns cucumber results when run handler is called successfully with a feature', () => { const Hapi = require('hapi'); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp({ serverHost: 'localhost', serverPort: 1234 }); chimp.options._ = {}; chimp.rerun = jest.fn().mockImplementation(callback => callback(null, [null, [null, 'cucumber results']])); chimp.server(); const getHandler = Hapi.instance.route.mock.calls[1][0].handler; const request = { params: { absolutePath: 'blah' } }; const headerMock = jest.fn(); const reply = jest.fn().mockReturnValue({ header: headerMock }); getHandler(request, reply); expect(chimp.options._[2]).toBe(request.params.absolutePath); expect(reply.mock.calls[0][0]).toBe('cucumber results'); }); it('returns "done" when interrupt handler is called successfully', () => { const Hapi = require('hapi'); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp({ serverHost: 'localhost', serverPort: 1234 }); chimp.interrupt = jest.fn().mockImplementation(callback => callback(null, [null, [null, 'cucumber results']])); chimp.server(); const interruptHandler = Hapi.instance.route.mock.calls[2][0].handler; const headerMock = jest.fn(); const reply = jest.fn().mockReturnValue({ header: headerMock }); interruptHandler(null, reply); expect(reply.mock.calls[0][0]).toBe('done'); }); it('returns cucumber results when runAll handler is called successfully', () => { const Hapi = require('hapi'); const Chimp = require('../lib/chimp.js'); const chimp = new Chimp({ serverHost: 'localhost', serverPort: 1234 }); chimp.rerun = jest.fn().mockImplementation(callback => callback(null, [null, [null, 'cucumber results']])); chimp.server(); const getHandler = Hapi.instance.route.mock.calls[3][0].handler; const headerMock = jest.fn(); const reply = jest.fn().mockReturnValue({ header: headerMock }); getHandler(null, reply); expect(reply.mock.calls[0][0]).toBe('cucumber results'); }); }); describe('run', () => { it('interrupts any existing processes, starts processes and calls callback', () => { const chimp = new Chimp(); chimp.interrupt = jest.fn().mockImplementation(callback => callback()); chimp._startProcesses = jest.fn().mockImplementation(callback => callback()); const callback = jest.fn(); chimp.run(callback); expect(chimp.interrupt.mock.calls.length).toBe(2); expect(chimp._startProcesses.mock.calls.length).toBe(1); expect(callback.mock.calls.length).toBe(1); }); it('detects errors in interrupt and calls callback with an error', () => { const chimp = new Chimp(); chimp.interrupt = jest.fn().mockImplementation(callback => callback('error')); const callback = jest.fn(); chimp.run(callback); expect(callback.mock.calls.length).toBe(1); expect(callback.mock.calls[0][0]).toEqual('error'); }); it('stops all processes on successful runs', () => { const chimp = new Chimp(); chimp.interrupt = jest.fn().mockImplementation(callback => callback()); chimp._startProcesses = jest.fn().mockImplementation(callback => callback()); chimp.stop = jest.fn(); const callback = jest.fn(); chimp.run(callback); expect(chimp.interrupt.mock.calls.length).toBe(2); }); it('passes the options to the simian reporter constructor', () => { const SimianReporter = require('../lib/simian-reporter'); const Chimp = require('../lib/chimp.js'); const options = { simianAccessToken: 'present' }; const chimp = new Chimp(options); chimp.interrupt = jest.fn().mockImplementation(callback => callback()); chimp._startProcesses = jest.fn().mockImplementation(callback => callback()); const callback = jest.fn(); chimp.run(callback); expect(SimianReporter.mock.calls[0][0]).toBe(options); expect(SimianReporter.mock.calls.length).toBe(1); }); it('calls the simian reporter when the run is finished', () => { jest.dontMock('../lib/simian-reporter'); const SimianReporter = require('../lib/simian-reporter'); SimianReporter.prototype.report = jest.fn(); const Chimp = require('../lib/chimp.js'); const options = { simianAccessToken: 'present' }; const chimp = new Chimp(options); chimp.interrupt = jest.fn().mockImplementation(callback => callback(null, 'hello')); chimp._startProcesses = jest.fn().mockImplementation(callback => callback(null, [undefined, '[]'])); const callback = jest.fn(); chimp.run(callback); expect(SimianReporter.instance.report.mock.calls.length).toBe(1); }); }); describe('interrupt', () => { it('calls interrupt on all processes in the reverse order that they were started', () => { jest.dontMock('async'); const Chimp = require('../lib/chimp'); const chimp = new Chimp(); let orderCounter = 0; function Process() { this.orderRun = -1; } Process.prototype.interrupt = function (callback) { this.orderRun = orderCounter++; callback(); }; const process1 = new Process('1'); const process2 = new Process('2'); chimp.processes = [process1, process2]; const callback = jest.fn(); chimp.interrupt(callback); expect(process2.orderRun).toBe(0); expect(process1.orderRun).toBe(1); }); it('bubbles callback without modifying the arguments', () => { const async = require('async'); const Chimp = require('../lib/chimp'); const chimp = new Chimp(); chimp.processes = [{ interrupt: jest.fn() }]; const someArgs = ['some', 'args']; async.series = jest.fn().mockImplementation(function (processes, callback) { callback.apply(this, someArgs); }); const callback = jest.fn(); chimp.interrupt(callback); expect(callback.mock.calls.length).toBe(1); expect(callback.mock.calls[0]).toEqual(['some', 'args']); }); it('calls the callback when no processes have been started', () => { const async = require('async'); const Chimp = require('../lib/chimp'); const chimp = new Chimp(); chimp.isInterrupting = true; async.series = jest.fn(); const callback = jest.fn(); chimp.interrupt(callback); expect(chimp.isInterrupting).toBe(false); expect(callback.mock.calls.length).toBe(1); expect(async.series.mock.calls.length).toBe(0); }); it('cancels the isInterrupting flag after all processes have run with no errors', () => { const _ = require('underscore'); const async = require('async'); const Chimp = require('../lib/chimp'); const chimp = new Chimp(); chimp.isInterrupting = true; chimp.processes = ['yo']; _.collect = jest.fn(); async.series = jest.fn().mockImplementation((procs, func) => { func(); }); chimp.interrupt(jest.fn()); expect(chimp.isInterrupting).toBe(false); }); it('cancels the isInterrupting flag after all processes have run with errors', () => { const _ = require('underscore'); const async = require('async'); const Chimp = require('../lib/chimp'); const chimp = new Chimp(); chimp.isInterrupting = true; chimp.processes = ['yo']; _.collect = jest.fn(); async.series = jest.fn().mockImplementation((procs, func) => { func('error'); }); chimp.interrupt(jest.fn()); expect(chimp.isInterrupting).toBe(false); }); }); describe('rerun', () => { it('calls run if interrupt is successful', () => { const chimp = new Chimp(); chimp.interrupt = jest.fn().mockImplementation(callback => { callback(null); }); chimp.run = jest.fn(); chimp.rerun(); expect(chimp.run.mock.calls.length).toBe(1); }); it('does not rerun if an rerun is in progress', () => { const chimp = new Chimp(); chimp.run = jest.fn(); chimp.isInterrupting = true; chimp.rerun(); expect(chimp.run.mock.calls.length).toBe(0); }); it('reruns once it has finished rerunning', () => { const chimp = new Chimp(); chimp.run = jest.fn().mockImplementation(callback => { callback(null); // after the first run, replace this mockImplementation with a standard mock so we // can assert on that the rerun interrupts after a successful run chimp.run = jest.fn(); }); chimp.rerun(); chimp.rerun(); expect(chimp.run.mock.calls.length).toBe(1); }); }); describe('_startProcesses', () => { it('creates an array of series of processes and starts them', () => { const async = require('async'); const Chimp = require('../lib/chimp.js'); async.series = jest.fn(); const chimp = new Chimp(); const processes = []; const mockedCreateProcesses = jest.spyOn(chimp, "_createProcesses"); mockedCreateProcesses.mockReturnValue(processes); chimp._startProcesses(); expect(mockedCreateProcesses.mock.calls.length).toBe(1); expect(chimp.processes).toBe(processes); mockedCreateProcesses.mockRestore(); }); it('start each process in its own context and calls callback once', () => { jest.dontMock('async'); const Chimp = require('../lib/chimp'); const chimp = new Chimp(); function Process() { this.state = 'constructed'; } Process.prototype.start = function (callback) { this.state = 'started'; callback(); }; const processes = [new Process(), new Process()]; const mockedCreateProcesses = jest.spyOn(chimp, "_createProcesses"); mockedCreateProcesses.mockReturnValue(processes); const callback = jest.fn(); chimp._startProcesses(callback); expect(typeof callback.mock.calls[0][0]).toBe('undefined'); expect(callback.mock.calls.length).toBe(1); expect(processes[0].state).toBe('started'); expect(processes[1].state).toBe('started'); mockedCreateProcesses.mockRestore(); }); it('bubbles up errors in callback if an processes callback with an error', () => { jest.dontMock('async'); const Chimp = require('../lib/chimp'); const chimp = new Chimp(); function Process() { this.state = 'constructed'; } Process.prototype.start = function (callback) { this.state = 'started'; callback('error!'); }; const processes = [new Process('1'), new Process('2')]; const mockedCreateProcesses = jest.spyOn(chimp, "_createProcesses"); mockedCreateProcesses.mockReturnValue(processes); const callback = jest.fn(); chimp._startProcesses(callback); expect(callback.mock.calls[0][0]).toBe('error!'); expect(callback.mock.calls.length).toBe(1); expect(processes[0].state).toBe('started'); expect(processes[1].state).toBe('constructed'); mockedCreateProcesses.mockRestore(); }); it('cancels the isInterrupting flag on error', () => { const _ = require('underscore'); const async = require('async'); const Chimp = require('../lib/chimp'); const chimp = new Chimp(); chimp.isInterrupting = true; const mockedCreateProcesses = jest.spyOn(chimp, "_createProcesses"); mockedCreateProcesses.mockImplementation(() => [{ start: { bind: () => 'yo' } }]); async.series = jest.fn().mockImplementation((procs, func) => { func('error'); }); chimp._startProcesses(jest.fn()); expect(chimp.isInterrupting).toBe(false); mockedCreateProcesses.mockRestore(); }); }); describe('_createProcesses', () => { it('adds a phantom', () => { const Phantom = require('../lib/phantom.js'); const Chimp = require('../lib/chimp.js'); const options = { browser: 'phantomjs' }; const chimp = new Chimp(options); const processes = chimp._createProcesses(); expect(Phantom.mock.calls[0][0]).toBe(options); expect(processes.length).toBe(2); }); it('adds a selenium when no browser is passed', () => { let Selenium = require('../lib/selenium.js'); const Chimp = require('../lib/chimp.js'); Selenium = jest.fn(); const options = { browser: 'some-browser', host: '' }; const chimp = new Chimp(options); const processes = chimp._createProcesses(); expect(Selenium.mock.calls[0][0]).toBe(options); expect(processes.length).toBe(2); }); it('does not add selenium when SauceLabs is the host', () => { let Selenium = require('../lib/selenium.js'); const Chimp = require('../lib/chimp.js'); Selenium = jest.fn(); const options = { host: 'saucelabs' }; const chimp = new Chimp(options); const processes = chimp._createProcesses(); expect(Selenium.mock.calls.length).toBe(0); expect(processes.length).toBe(1); }); it('adds cucumber last', () => { const Chimp = require('../lib/chimp.js'); const options = { browser: 'phantomjs' }; const chimp = new Chimp(options); const processes = chimp._createProcesses(); expect(typeof processes[0].cucumberChild).toBe('undefined'); expect(typeof processes[1].cucumberChild).not.toBe('undefined'); }); it('should add at least one process', () => { const chimp = new Chimp({ browser: 'phantomjs' }); const processes = chimp._createProcesses(); expect(processes.length).not.toBe(0); }); }); });