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

Run default command if it's not just * #1062

Merged
merged 12 commits into from Dec 11, 2019
Merged
16 changes: 10 additions & 6 deletions index.js
Expand Up @@ -360,8 +360,12 @@ Command.prototype.action = function(fn) {
fn.apply(self, actionArgs);
};
var parent = this.parent || this;
var name = parent === this ? '*' : this._name;
parent.on('command:' + name, listener);
if (parent === this) {
parent.on('program-command', listener);
} else {
parent.on('command:' + this._name, listener);
}

if (this._alias) parent.on('command:' + this._alias, listener);
return this;
};
Expand Down Expand Up @@ -798,19 +802,19 @@ Command.prototype.parseArgs = function(args, unknown) {
if (this.listeners('command:' + name).length) {
this.emit('command:' + args.shift(), args, unknown);
} else {
shadowspawn marked this conversation as resolved.
Show resolved Hide resolved
this.emit('program-command', args, unknown);
this.emit('command:*', args, unknown);
}
} else {
outputHelpIfNecessary(this, unknown);

// If there were no args and we have unknown options,
// then they are extraneous and we need to error.
if (unknown.length > 0 && !this.defaultExecutable) {
this.unknownOption(unknown[0]);
}
if (this.commands.length === 0 &&
this._args.filter(function(a) { return a.required; }).length === 0) {
this.emit('command:*');
// Call the program action handler, unless it has a (missing) required parameter and signature does not match.
if (this._args.filter(function(a) { return a.required; }).length === 0) {
this.emit('program-command');
}
}

Expand Down
29 changes: 27 additions & 2 deletions tests/command.action.test.js
Expand Up @@ -35,7 +35,7 @@ test('when .action called with extra arguments then extras also passed to action
expect(actionMock).toHaveBeenCalledWith('my-file', cmd, ['a']);
});

test('when .action on program with argument then action called', () => {
test('when .action on program with required argument and argument supplied then action called', () => {
const actionMock = jest.fn();
const program = new commander.Command();
program
Expand All @@ -45,6 +45,16 @@ test('when .action on program with argument then action called', () => {
expect(actionMock).toHaveBeenCalledWith('my-file', program);
});

test('when .action on program with required argument and argument not supplied then action not called', () => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.arguments('<file>')
.action(actionMock);
program.parse(['node', 'test']);
expect(actionMock).not.toHaveBeenCalled();
});

// Changes made in #729 to call program action handler
test('when .action on program and no arguments then action called', () => {
const actionMock = jest.fn();
Expand Down Expand Up @@ -75,7 +85,7 @@ test('when .action on program without optional argument supplied then action cal
expect(actionMock).toHaveBeenCalledWith(undefined, program);
});

test('when .action on program with subcommand and program argument then program action called', () => {
test('when .action on program with optional argument and subcommand and program argument then program action called', () => {
const actionMock = jest.fn();
const program = new commander.Command();
program
Expand All @@ -88,3 +98,18 @@ test('when .action on program with subcommand and program argument then program

expect(actionMock).toHaveBeenCalledWith('a', program);
});

// Changes made in #1062 to allow this case
test('when .action on program with optional argument and subcommand and no program argument then program action called', () => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.arguments('[file]')
.action(actionMock);
program
.command('subcommand');

program.parse(['node', 'test']);

expect(actionMock).toHaveBeenCalledWith(undefined, program);
});
124 changes: 96 additions & 28 deletions tests/command.asterisk.test.js
@@ -1,34 +1,102 @@
const commander = require('../');

test('when no arguments then asterisk action not called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('install')
.action(mockAction);
program.parse(['node', 'test']);
expect(mockAction).not.toHaveBeenCalled();
});
// .command('*') is the old main/default command handler. It adds a listener
// for 'command:*'. It has been somewhat replaced by the program action handler,
// so most uses are probably old code. Current plan is keep the code backwards compatible
// and put work in elsewhere for new code (e.g. evolving behaviour for program action handler).
//
// The event 'command:*' is also listened for directly for testing for unknown commands
// due to an example in the README, although this is not robust (e.g. sent for git-style commands).
//
// Historical: the event 'command:*' used to also be shared by the action handler on the program.

describe(".command('*')", () => {
test('when no arguments then asterisk action not called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('*')
.action(mockAction);
program.parse(['node', 'test']);
expect(mockAction).not.toHaveBeenCalled();
});

test('when unrecognised argument then asterisk action called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('*')
.action(mockAction);
program.parse(['node', 'test', 'unrecognised-command']);
expect(mockAction).toHaveBeenCalled();
});

test('when recognised command then asterisk action not called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('install')
.action(() => { });
program
.command('*')
.action(mockAction);
program.parse(['node', 'test', 'install']);
expect(mockAction).not.toHaveBeenCalled();
});

test('when recognised command then asterisk action not called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('install')
.action(() => { });
program
.action(mockAction);
program.parse(['node', 'test', 'install']);
expect(mockAction).not.toHaveBeenCalled();
test('when unrecognised command/argument then asterisk action called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('install');
program
.command('*')
.action(mockAction);
program.parse(['node', 'test', 'unrecognised-command']);
expect(mockAction).toHaveBeenCalled();
});
});

test('when unrecognised command/argument then asterisk action called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('install');
program
.action(mockAction);
program.parse(['node', 'test', 'unrecognised-command']);
expect(mockAction).toHaveBeenCalled();
// Test .on explicitly rather than assuming covered by .command
describe(".on('command:*')", () => {
test('when no arguments then listener not called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.on('command:*', mockAction);
program.parse(['node', 'test']);
expect(mockAction).not.toHaveBeenCalled();
});

test('when unrecognised argument then listener called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.on('command:*', mockAction);
program.parse(['node', 'test', 'unrecognised-command']);
expect(mockAction).toHaveBeenCalled();
});

test('when recognised command then listener not called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('install')
.action(() => { });
program
.on('command:*', mockAction);
program.parse(['node', 'test', 'install']);
expect(mockAction).not.toHaveBeenCalled();
});

test('when unrecognised command/argument then listener called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('install');
program
.on('command:*', mockAction);
program.parse(['node', 'test', 'unrecognised-command']);
expect(mockAction).toHaveBeenCalled();
});
});