Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to Jest as testing framework #1035

Merged
merged 88 commits into from Sep 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
1580ac8
Prototype Jest as testing framework
shadowspawn Jul 28, 2019
e3ed9ea
Merge with package conflicts
shadowspawn Aug 31, 2019
d394b38
Add comment to direct ports
shadowspawn Aug 31, 2019
a5424f0
Add bool tests, not direct ports
shadowspawn Aug 31, 2019
ac86821
Add tests for option with required value
shadowspawn Aug 31, 2019
b5d3b81
Add jest tests for optional option
shadowspawn Aug 31, 2019
eaa2805
Add tests for options follow by options
shadowspawn Aug 31, 2019
6f5159c
Add multiple short flags test
shadowspawn Aug 31, 2019
4b8095a
Improve test description
shadowspawn Sep 1, 2019
7defa68
Add single dash test
shadowspawn Sep 1, 2019
935b0ec
Add idea for value tests
shadowspawn Sep 1, 2019
503efb7
Add old camelcase tests and new flags tests
shadowspawn Sep 1, 2019
7ce8302
Add more testing ideas
shadowspawn Sep 1, 2019
fd85aae
Add testing for custom option processing
shadowspawn Sep 1, 2019
f677603
Add extra tests for non-boolean default
shadowspawn Sep 1, 2019
ba12318
Add placeholder for test file skipped on first pass
shadowspawn Sep 1, 2019
6c4aaa9
Add comments above describe groups to make more obvious
shadowspawn Sep 1, 2019
c27f2af
Change option names to make easier to understand (more realistic)
shadowspawn Sep 1, 2019
ba2ec22
Expand overview comment
shadowspawn Sep 1, 2019
818f791
Add tests for the ways values are specified
shadowspawn Sep 1, 2019
a093936
Add Jest tests for .opts()
shadowspawn Sep 1, 2019
250e5fc
Add negative number as an interesting dash case
shadowspawn Sep 1, 2019
915f98d
Add Jest dashdash test for stop processing options
shadowspawn Sep 1, 2019
5fa0dd9
Add Jest regex tests
shadowspawn Sep 1, 2019
8b61a81
Move dash testing and include full variety of ways values are specified
shadowspawn Sep 1, 2019
27fca89
Rename spy to mock
shadowspawn Sep 1, 2019
54f3a93
Add Jest .version tests
shadowspawn Sep 1, 2019
ced3267
Port literal test and subsume dashdash test
shadowspawn Sep 2, 2019
19387d1
Remove implemented test notes
shadowspawn Sep 2, 2019
af0225e
Port some of command.name tests
shadowspawn Sep 2, 2019
008add8
Add tests when have version command and .version
shadowspawn Sep 2, 2019
3e9d814
Add note that whitebox test
shadowspawn Sep 2, 2019
d8fd6eb
Add variadic test
shadowspawn Sep 2, 2019
64c85e8
Add experimental _exitOverride to allow throw instead of process.exit
shadowspawn Sep 3, 2019
6a30bf7
Rework version tests using experimental exitOverride
shadowspawn Sep 3, 2019
e384be2
Add empty string test to option values
shadowspawn Sep 4, 2019
8c4a2f2
Add test for command alias appearing in help
shadowspawn Sep 4, 2019
b08001a
Add unknownOption support for exitOverride
shadowspawn Sep 4, 2019
6872933
Add test for allowUnknownOption
shadowspawn Sep 4, 2019
4242ea7
Call added commands subcommand to clarify
shadowspawn Sep 4, 2019
21fdbaa
Add tests for .usage
shadowspawn Sep 4, 2019
b998f36
Note to tidy up how testing strings for better error messages
shadowspawn Sep 4, 2019
d3fe19f
Add first signal test in Jest
shadowspawn Sep 5, 2019
a3bdf2a
Make new signal listener more predictable
shadowspawn Sep 5, 2019
dca88f7
Add @types/jest
shadowspawn Sep 6, 2019
c338f77
Add full set of signals to test
shadowspawn Sep 6, 2019
90b00ba
Add Jest --inspect tests
shadowspawn Sep 6, 2019
4bf6845
Add Jest lookup tests
shadowspawn Sep 6, 2019
172ab24
Add exitOveride support to variadicArgNotLast
shadowspawn Sep 6, 2019
09af1b2
Switch to execFile
shadowspawn Sep 6, 2019
374842d
Switch to execFIle (and formally async)
shadowspawn Sep 6, 2019
87d8a8f
Fix typos
shadowspawn Sep 6, 2019
984c57c
Make case consistent in filename
shadowspawn Sep 7, 2019
498eb57
Add more Jest alias tests
shadowspawn Sep 7, 2019
5a7ba23
Add Jest executableSubcommand tests for default and executableFile
shadowspawn Sep 7, 2019
b2a07c1
Add Jest subsubcommand test
shadowspawn Sep 7, 2019
c267edf
Add to Jest alias tests
shadowspawn Sep 7, 2019
6d62d93
Add Jest test for .commandHelp
shadowspawn Sep 7, 2019
375412e
Delete a couple of ported tests covered better elsewhere
shadowspawn Sep 7, 2019
20abf4a
Replace more process.exit with exit override
shadowspawn Sep 7, 2019
bab6a17
Add Jest help tests
shadowspawn Sep 7, 2019
2a3a9a1
Add Jest helpOption tests
shadowspawn Sep 7, 2019
5d04cec
Add Jest noHelp test
shadowspawn Sep 7, 2019
ee1745d
Add Jest asterisk test
shadowspawn Sep 7, 2019
38cb991
Not porting complex generic command tests
shadowspawn Sep 7, 2019
f647cef
Jest test for executable with no command
shadowspawn Sep 7, 2019
005a80f
Add default throw for _exitOverride, and tidy _exit and CommanderErro…
shadowspawn Sep 8, 2019
94af7b1
Add Jest tests for _exitOverride and simplify usage with new default …
shadowspawn Sep 8, 2019
d0d7c12
Added commented out tests for process.exit called after spawn
shadowspawn Sep 8, 2019
85cb367
Fix typo
shadowspawn Sep 9, 2019
bf59c87
Add test on error class, because calling code may rely on it
shadowspawn Sep 9, 2019
cf990a4
Clarify parameter type for exitOverride callback
shadowspawn Sep 9, 2019
83c140a
Lint
shadowspawn Sep 9, 2019
2533d05
Add test matching issue #1039
shadowspawn Sep 9, 2019
100713d
Add test showing #1032
shadowspawn Sep 9, 2019
e8f16eb
Skip open issues normally
shadowspawn Sep 9, 2019
294e2e0
Switch to Jest
shadowspawn Sep 11, 2019
846c851
Fix error detection on node 8
shadowspawn Sep 12, 2019
0b04bc6
Change exitOveride to allow carrying on, and intercept exits from exe…
shadowspawn Sep 14, 2019
c6e6e27
Rework exitOverride for async
shadowspawn Sep 14, 2019
df742d4
Replace listen with listen2 code: lint, and produce output so caller …
shadowspawn Sep 16, 2019
bfa32be
Add explanation in functional empty file
shadowspawn Sep 16, 2019
04e6cab
Lint on pm test files, and remove the listen2 missed in earlier commit
shadowspawn Sep 16, 2019
6b74f14
Merge branch 'develop' into feature/jest
shadowspawn Sep 16, 2019
56880e2
Add more test for open issues
shadowspawn Sep 17, 2019
92b77d2
Remove testing ideas, not tracking them in file
shadowspawn Sep 20, 2019
0188e22
Add tests to `npm run lint` and fix errors
shadowspawn Sep 20, 2019
c462f2c
Rename skipped tests for open issues to avoid warning in normal lint
shadowspawn Sep 20, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
@@ -1,5 +1,6 @@
{
"extends": "standard",
"extends": ["standard", "plugin:jest/recommended"],
"plugins": ["jest"],
"rules": {
"one-var": "off",
"semi": ["error", "always"],
Expand Down
119 changes: 105 additions & 14 deletions index.js
Expand Up @@ -87,6 +87,30 @@ Option.prototype.is = function(arg) {
return this.short === arg || this.long === arg;
};

/**
* CommanderError class
* @class
*/
class CommanderError extends Error {
/**
* Constructs the CommanderError class
* @param {Number} exitCode suggested exit code which could be used with process.exit
* @param {String} code an id string representing the error
* @param {String} message human-readable description of the error
* @constructor
*/
constructor(exitCode, code, message) {
super(message);
// properly capture stack trace in Node.js
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.code = code;
this.exitCode = exitCode;
}
}

exports.CommanderError = CommanderError;

/**
* Initialize a new `Command`.
*
Expand Down Expand Up @@ -157,6 +181,8 @@ Command.prototype.command = function(nameAndArgs, actionOptsOrExecDesc, execOpts
cmd._helpDescription = this._helpDescription;
cmd._helpShortFlag = this._helpShortFlag;
cmd._helpLongFlag = this._helpLongFlag;
cmd._exitCallback = this._exitCallback;

cmd._executableFile = opts.executableFile; // Custom name for executable file
this.commands.push(cmd);
cmd.parseExpectedArgs(args);
Expand Down Expand Up @@ -228,6 +254,47 @@ Command.prototype.parseExpectedArgs = function(args) {
return this;
};

/**
* Register callback `fn` to use as replacement for calling process.exit.
*
* @param {Function} fn callback which will be passed a CommanderError
* @return {Command} for chaining
* @api public
*/

Command.prototype._exitOverride = function(fn) {
if (fn) {
this._exitCallback = fn;
} else {
this._exitCallback = function(err) {
if (err.code !== 'commander.executeSubCommandAsync') {
throw err;
} else {
// Async callback from spawn events, not useful to throw.
}
};
}
return this;
};

/**
* Call process.exit, and _exitCallback if defined.
*
* @param {Number} exitCode exit code for using with process.exit
* @param {String} code an id string representing the error
* @param {String} message human-readable description of the error
* @return never
* @api private
*/

Command.prototype._exit = function(exitCode, code, message) {
if (this._exitCallback) {
this._exitCallback(new CommanderError(exitCode, code, message));
// Expecting this line is not reached.
}
process.exit(exitCode);
};

/**
* Register callback `fn` for the command.
*
Expand Down Expand Up @@ -586,14 +653,30 @@ Command.prototype.executeSubCommand = function(argv, args, unknown, executableFi
}
});
});
proc.on('close', process.exit.bind(process));

// By default terminate process when spawned process terminates.
// Suppressing the exit if exitCallback defined is a bit messy and of limited use, but does allow process to stay running!
const exitCallback = this._exitCallback;
if (!exitCallback) {
proc.on('close', process.exit.bind(process));
} else {
proc.on('close', () => {
exitCallback(new CommanderError(process.exitCode || 0, 'commander.executeSubCommandAsync', '(close)'));
});
}
proc.on('error', function(err) {
if (err.code === 'ENOENT') {
console.error('error: %s(1) does not exist, try --help', bin);
} else if (err.code === 'EACCES') {
console.error('error: %s(1) not executable. try chmod or run with root', bin);
}
process.exit(1);
if (!exitCallback) {
process.exit(1);
} else {
const wrappedError = new CommanderError(1, 'commander.executeSubCommandAsync', '(error)');
wrappedError.nestedError = err;
exitCallback(wrappedError);
}
});

// Store the reference to the child process
Expand Down Expand Up @@ -810,8 +893,9 @@ Command.prototype.opts = function() {
*/

Command.prototype.missingArgument = function(name) {
console.error("error: missing required argument '%s'", name);
process.exit(1);
const message = `error: missing required argument '${name}'`;
console.error(message);
this._exit(1, 'commander.missingArgument', message);
};

/**
Expand All @@ -823,12 +907,14 @@ Command.prototype.missingArgument = function(name) {
*/

Command.prototype.optionMissingArgument = function(option, flag) {
let message;
if (flag) {
console.error("error: option '%s' argument missing, got '%s'", option.flags, flag);
message = `error: option '${option.flags}' argument missing, got '${flag}'`;
} else {
console.error("error: option '%s' argument missing", option.flags);
message = `error: option '${option.flags}' argument missing`;
}
process.exit(1);
console.error(message);
this._exit(1, 'commander.optionMissingArgument', message);
};

/**
Expand All @@ -840,8 +926,9 @@ Command.prototype.optionMissingArgument = function(option, flag) {

Command.prototype.unknownOption = function(flag) {
if (this._allowUnknownOption) return;
console.error("error: unknown option '%s'", flag);
process.exit(1);
const message = `error: unknown option '${flag}'`;
console.error(message);
this._exit(1, 'commander.unknownOption', message);
};

/**
Expand All @@ -852,8 +939,9 @@ Command.prototype.unknownOption = function(flag) {
*/

Command.prototype.variadicArgNotLast = function(name) {
console.error("error: variadic arguments must be last '%s'", name);
process.exit(1);
const message = `error: variadic arguments must be last '${name}'`;
console.error(message);
this._exit(1, 'commander.variadicArgNotLast', message);
};

/**
Expand Down Expand Up @@ -881,7 +969,7 @@ Command.prototype.version = function(str, flags, description) {
this.options.push(versionOption);
this.on('option:' + this._versionOptionName, function() {
process.stdout.write(str + '\n');
process.exit(0);
this._exit(0, 'commander.version', str);
});
return this;
};
Expand Down Expand Up @@ -1208,7 +1296,9 @@ Command.prototype.helpOption = function(flags, description) {

Command.prototype.help = function(cb) {
this.outputHelp(cb);
process.exit();
// exitCode: preserving original behaviour which was calling process.exit()
// message: do not have all displayed text available so only passing placeholder.
this._exit(process.exitCode || 0, 'commander.help', '(outputHelp)');
};

/**
Expand Down Expand Up @@ -1253,7 +1343,8 @@ function outputHelpIfNecessary(cmd, options) {
for (var i = 0; i < options.length; i++) {
if (options[i] === cmd._helpLongFlag || options[i] === cmd._helpShortFlag) {
cmd.outputHelp();
process.exit(0);
// (Do not have all displayed text available so only passing placeholder.)
cmd._exit(0, 'commander.helpDisplayed', '(outputHelp)');
}
}
}
Expand Down