Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import esLintjs from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier';
import nodeTest from 'eslint-node-test';

// Only run tseslint on the files that we have included for TypeScript.
const tsconfigTsFiles = ['**/*.{ts,mts}']; // match "include" in tsconfig.ts.json;
Expand Down Expand Up @@ -65,4 +66,17 @@ export default defineConfig(
],
},
},
{
files: ['**/*.js'],
plugins: {
'node-test': nodeTest,
},
extends: ['node-test/recommended'],
rules: {
'node-test/prefer-test-context-assert': 'off', // we use callback parameter t to generate mocks, but do not want to use t.assertX (as t.assertX not automatically strict)
'node-test/no-useless-assertion': 'off', // we use `assert.doesNotThrow()` as only assert in multiple tests (so removing that assert triggers a different lint error)
'node-test/no-process-env-mutation': 'off', // we manage env in ways node-test does not recognise
'node-test/require-top-level-describe': 'error', // Enforce top-level describe for providing context in test output. Disable by hand on single test files.
},
},
);
2 changes: 0 additions & 2 deletions examples/help-centered.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ class MyCommand extends Command {

const program = new MyCommand();

program.configureHelp({ MyCommand });

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bogus call I spotted myself, not one reported by the linter.


program
.option('-s', 'short flag')
.option('-f, --flag', 'short and long flag')
Expand Down
40 changes: 37 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
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@types/node": "^22.7.4",
"eslint": "^10.0.2",
"eslint-config-prettier": "^10.0.1",
"eslint-node-test": "^0.4.0",
"globals": "^17.3.0",
"prettier": "^3.2.5",
"tsd": "^0.33.0",
Expand Down
18 changes: 12 additions & 6 deletions tests/argument.choices.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ describe('Argument.choices()', () => {
program.addArgument(
new commander.Argument('<shade>').choices(['red', 'blue']),
);
assert.throws(() => {
program.parse(['orange'], { from: 'user' });
});
assert.throws(
() => {
program.parse(['orange'], { from: 'user' });
},
{ code: 'commander.invalidArgument' },
);
});
});

Expand All @@ -50,8 +53,11 @@ describe('Argument.choices() parameter is treated as readonly, per TypeScript de
const param = ['red', 'blue'];
program.addArgument(new commander.Argument('<shade>').choices(param));
param.push('orange');
assert.throws(() => {
program.parse(['orange'], { from: 'user' });
});
assert.throws(
() => {
program.parse(['orange'], { from: 'user' });
},
{ code: 'commander.invalidArgument' },
);
});
});
2 changes: 1 addition & 1 deletion tests/argument.custom-processing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ describe('custom processing function for command-argument', () => {
const program = new commander.Command();
assert.throws(() => {
program.argument('<number>', 'float argument', 4);
});
}, /a default value for a required argument is never used/);
});

test('when custom processing for argument throws plain error then not CommanderError caught', () => {
Expand Down
15 changes: 1 addition & 14 deletions tests/argument.variadic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import assert from 'node:assert/strict';

// Testing variadic arguments. Testing all the action arguments, but could test just variadicArg.

describe('Command variadic argument using .argument()', (t) => {
describe('Command variadic argument using .argument()', () => {
test('when no extra arguments specified for program then variadic arg is empty array', (t) => {
const actionMock = t.mock.fn();
const program = new commander.Command();
Expand Down Expand Up @@ -117,17 +117,4 @@ describe('Command variadic argument using .argument()', (t) => {
program.parse(['one', 'two'], { from: 'user' });
assert.deepEqual(passedArg, ['one', 'two']);
});

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate of above test

test('when variadic has default array then specified value is used instead of default (not appended)', () => {
const program = new commander.Command();
let passedArg;
program
.addArgument(new commander.Argument('[value...]').default(['DEFAULT']))
.action((value) => {
passedArg = value;
});

program.parse(['one', 'two'], { from: 'user' });
assert.deepEqual(passedArg, ['one', 'two']);
});
});
9 changes: 6 additions & 3 deletions tests/command.action.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ describe('Command.action()', () => {
test(`via ${methodName}`, (t) => {
const actionMock = t.mock.fn();
program.action(actionMock);
assert.throws(() => {
program.parse(['node', 'test']);
});
assert.throws(
() => {
program.parse(['node', 'test']);
},
{ code: 'commander.missingArgument' },
);
assert.equal(actionMock.mock.callCount(), 0);
});
});
Expand Down
3 changes: 2 additions & 1 deletion tests/command.addCommand.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('Command.addCommand()', () => {
case 'number':
case 'undefined':
// Compare values in a way that will be readable in test failure message.
// eslint-disable-next-line node-test/no-conditional-assertion
assert.equal(`${key}:${cmd1[key]}`, `${key}:${cmd2[key]}`);
break;
}
Expand All @@ -47,7 +48,7 @@ describe('Command.addCommand()', () => {
const cmd = new commander.Command();
assert.throws(() => {
program.addCommand(cmd);
});
}, /Command passed to \.addCommand\(\) must have a name/);
});

test('when executable command with custom executableFile passed to .addCommand then ok', () => {
Expand Down
65 changes: 43 additions & 22 deletions tests/command.addHelpText.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ describe('Command.addHelpText(): program calls to addHelpText', () => {
const program = new commander.Command();
assert.throws(() => {
program.addHelpText('silly', 'text');
});
}, /Unexpected value for position to addHelpText/);
});
});

Expand Down Expand Up @@ -157,18 +157,24 @@ describe('Command.addHelpText(): context checks with full parse', () => {
test('when help requested then text is on stdout', () => {
const program = new commander.Command();
program.exitOverride().addHelpText('before', 'text');
assert.throws(() => {
program.parse(['--help'], { from: 'user' });
});
assert.throws(
() => {
program.parse(['--help'], { from: 'user' });
},
{ code: 'commander.helpDisplayed' },
);
assert.equal(stdoutSpy.mock.calls[0].arguments[0], 'text\n');
});

test('when help for error then text is on stderr', () => {
const program = new commander.Command();
program.exitOverride().addHelpText('before', 'text').command('sub');
assert.throws(() => {
program.parse([], { from: 'user' });
});
assert.throws(
() => {
program.parse([], { from: 'user' });
},
{ code: 'commander.help' },
);
assert.equal(stderrSpy.mock.calls[0].arguments[0], 'text\n');
});

Expand All @@ -178,9 +184,12 @@ describe('Command.addHelpText(): context checks with full parse', () => {
program.exitOverride().addHelpText('before', (contextParam) => {
context = contextParam;
});
assert.throws(() => {
program.parse(['--help'], { from: 'user' });
});
assert.throws(
() => {
program.parse(['--help'], { from: 'user' });
},
{ code: 'commander.helpDisplayed' },
);
assert.equal(context.error, false);
});

Expand All @@ -193,9 +202,12 @@ describe('Command.addHelpText(): context checks with full parse', () => {
context = contextParam;
})
.command('sub');
assert.throws(() => {
program.parse([], { from: 'user' });
});
assert.throws(
() => {
program.parse([], { from: 'user' });
},
{ code: 'commander.help' },
);
assert.equal(context.error, true);
});

Expand All @@ -205,9 +217,12 @@ describe('Command.addHelpText(): context checks with full parse', () => {
program.exitOverride().addHelpText('before', (contextParam) => {
context = contextParam;
});
assert.throws(() => {
program.parse(['--help'], { from: 'user' });
});
assert.throws(
() => {
program.parse(['--help'], { from: 'user' });
},
{ code: 'commander.helpDisplayed' },
);
assert.equal(context.command, program);
});

Expand All @@ -218,9 +233,12 @@ describe('Command.addHelpText(): context checks with full parse', () => {
const sub = program.command('sub').addHelpText('before', (contextParam) => {
context = contextParam;
});
assert.throws(() => {
program.parse(['sub', '--help'], { from: 'user' });
});
assert.throws(
() => {
program.parse(['sub', '--help'], { from: 'user' });
},
{ code: 'commander.helpDisplayed' },
);
assert.equal(context.command, sub);
});

Expand All @@ -231,9 +249,12 @@ describe('Command.addHelpText(): context checks with full parse', () => {
context = contextParam;
});
const sub = program.command('sub');
assert.throws(() => {
program.parse(['sub', '--help'], { from: 'user' });
});
assert.throws(
() => {
program.parse(['sub', '--help'], { from: 'user' });
},
{ code: 'commander.helpDisplayed' },
);
assert.equal(context.command, sub);
});
});
Loading