Skip to content

Commit

Permalink
Extend tests for --watch options
Browse files Browse the repository at this point in the history
This commit adds two test cases to test the `--watch` option. We check
that touching a test file reruns the tests and we test that touching a
file that has a correct extensions reruns the test.

This commit adds `fs-extra` as a new dev dependency.
  • Loading branch information
Thomas Scholtes committed May 14, 2019
1 parent ffbcbf6 commit 5a2124b
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 54 deletions.
1 change: 1 addition & 0 deletions .eslintrc.yml
Expand Up @@ -25,6 +25,7 @@ overrides:
- bin/*
- lib/cli/**/*.js
- test/node-unit/**/*.js
- test/integration/**/*.js
- lib/growl.js
parserOptions:
ecmaVersion: 6
Expand Down
46 changes: 43 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -550,6 +550,7 @@
"eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"fs-extra": "^8.0.1",
"husky": "^1.3.1",
"jsdoc": "^3.5.5",
"karma": "^4.0.1",
Expand Down
37 changes: 21 additions & 16 deletions test/integration/helpers.js
Expand Up @@ -115,31 +115,36 @@ module.exports = {
},
/**
* Invokes the mocha binary for the given fixture using the JSON reporter,
* returning the **raw** string output, as well as exit code.
* returning the child process and a promise for the results of running the
* command. The result includes the **raw** string output, as well as exit code.
*
* By default, `STDERR` is ignored. Pass `{stdio: 'pipe'}` as `opts` if you
* want it.
* @param {string} fixturePath - Path from __dirname__
* @param {string[]} args - Array of args
* @param {Function} fn - Callback
* @param {Object} [opts] - Opts for `spawn()`
* @returns {string} Raw output
* @returns {[ChildProcess|Promise<Result>]}
*/
runMochaJSONRaw: function(fixturePath, args, fn, opts) {
var path;

path = resolveFixturePath(fixturePath);
runMochaJSONRawAsync: function(fixturePath, args, opts) {
const path = resolveFixturePath(fixturePath);
args = args || [];

return invokeSubMocha(
args.concat(['--reporter', 'json', path]),
function(err, resRaw) {
if (err) return fn(err);
let childProcess;
const resultPromise = new Promise((resolve, reject) => {
childProcess = invokeSubMocha(
args.concat(['--reporter', 'json', path]),
function(err, resRaw) {
if (err) {
reject(err);
} else {
resolve(resRaw);
}
},
opts
);
});

fn(null, resRaw);
},
opts
);
return [childProcess, resultPromise];
},

/**
Expand Down Expand Up @@ -282,7 +287,7 @@ function resolveFixturePath(fixture) {
if (path.extname(fixture) !== '.js') {
fixture += '.fixture.js';
}
return path.join('test', 'integration', 'fixtures', fixture);
return path.resolve('test', 'integration', 'fixtures', fixture);
}

function getSummary(res) {
Expand Down
190 changes: 155 additions & 35 deletions test/integration/options/watch.spec.js
@@ -1,16 +1,18 @@
'use strict';

var helpers = require('../helpers');
var runMochaJSONRaw = helpers.runMochaJSONRaw;
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const helpers = require('../helpers');
const runMochaJSONRawAsync = helpers.runMochaJSONRawAsync;

describe('--watch', function() {
var args = [];

before(function() {
args = ['--watch'];
});
const sigintExitCode = 130;

describe('--watch', function() {
describe('when enabled', function() {
this.timeout(10 * 1000);
this.slow(3000);

before(function() {
// Feature works but SIMULATING the signal (ctrl+c) via child process
// does not work due to lack of POSIX signal compliance on Windows.
Expand All @@ -19,38 +21,156 @@ describe('--watch', function() {
}
});

it('should show the cursor and signal correct exit code, when watch process is terminated', function(done) {
this.timeout(0);
this.slow(3000);

var fixture = 'exit.fixture.js';
var spawnOpts = {stdio: 'pipe'};
var mocha = runMochaJSONRaw(
fixture,
args,
function postmortem(err, data) {
if (err) {
return done(err);
}

var expectedCloseCursor = '\u001b[?25h';
beforeEach(function() {
this.tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mocha-'));

var fixtureSource = helpers.resolveFixturePath(helpers.DEFAULT_FIXTURE);

this.testFile = path.join(this.tempDir, 'test.js');
fs.copySync(fixtureSource, this.testFile);
});

afterEach(function() {
if (this.tempDir) {
return fs.remove(this.tempDir);
}
});

it('should show the cursor and signal correct exit code, when watch process is terminated', function() {
const [mocha, resultPromise] = runMochaJSONRawAsync(
helpers.DEFAULT_FIXTURE,
['--watch']
);

return sleep(1000)
.then(() => {
mocha.kill('SIGINT');
return resultPromise;
})
.then(data => {
const expectedCloseCursor = '\u001b[?25h';
expect(data.output, 'to contain', expectedCloseCursor);

function exitStatusBySignal(sig) {
return 128 + sig;
}
expect(data.code, 'to be', sigintExitCode);
});
});

it('reruns test when watched test file is touched', function() {
const [mocha, outputPromise] = runMochaJSONWatchAsync(this.testFile, [], {
cwd: this.tempDir
});

var sigint = 2;
expect(data.code, 'to be', exitStatusBySignal(sigint));
done();
},
spawnOpts
return sleep(1000)
.then(() => {
touchFile(this.testFile);
return sleep(1000);
})
.then(() => {
mocha.kill('SIGINT');
return outputPromise;
})
.then(results => {
expect(results, 'to have length', 2);
});
});

it('reruns test when file matching extension is touched', function() {
const watchedFile = path.join(this.tempDir, 'file.xyz');
touchFile(watchedFile);
const [mocha, outputPromise] = runMochaJSONWatchAsync(
this.testFile,
['--extension', 'xyz,js'],
{
cwd: this.tempDir
}
);

setTimeout(function() {
// Kill the child process
mocha.kill('SIGINT');
}, 1000);
return sleep(1000)
.then(() => {
touchFile(watchedFile);
return sleep(1000);
})
.then(() => {
mocha.kill('SIGINT');
return outputPromise;
})
.then(results => {
expect(results, 'to have length', 2);
});
});

it('ignores files in "node_modules" and ".git"', function() {
const nodeModulesFile = path.join(
this.tempDir,
'node_modules',
'file.xyz'
);
const gitFile = path.join(this.tempDir, '.git', 'file.xyz');

touchFile(gitFile);
touchFile(nodeModulesFile);

const [mocha, outputPromise] = runMochaJSONWatchAsync(
this.testFile,
['--extension', 'xyz,js'],
{
cwd: this.tempDir
}
);

return sleep(2000)
.then(() => {
touchFile(gitFile);
touchFile(nodeModulesFile);
})
.then(() => sleep(2000))
.then(() => {
mocha.kill('SIGINT');
return outputPromise;
})
.then(results => {
expect(results, 'to have length', 1);
});
});
});
});

/**
* Invokes the mocha binary with the `--watch` argument for the given fixture.
*
* Returns child process and a promise for the test results. The test results
* are an array of JSON objects generated by the JSON reporter.
*
* Checks that the exit code of the mocha command is 130, i.e. mocha was killed
* by SIGINT.
*/
function runMochaJSONWatchAsync(fixture, args, spawnOpts) {
args = ['--watch'].concat(args);
const [mocha, mochaDone] = runMochaJSONRawAsync(fixture, args, spawnOpts);
const testResults = mochaDone.then(data => {
expect(data.code, 'to be', sigintExitCode);

const testResults = data.output
// eslint-disable-next-line no-control-regex
.replace(/\u001b\[\?25./g, '')
.split('\u001b[2K')
.map(x => JSON.parse(x));
return testResults;
});
return [mocha, testResults];
}

/**
* Touch a file by appending a space to the end. Returns a promise that resolves
* when the file has been touched.
*/
function touchFile(file) {
fs.ensureDirSync(path.dirname(file));
fs.appendFileSync(file, ' ');
}

function sleep(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}

0 comments on commit 5a2124b

Please sign in to comment.