Skip to content

Commit 488a2a9

Browse files
committed
feat: add default options
1 parent 16d7100 commit 488a2a9

File tree

6 files changed

+119
-53
lines changed

6 files changed

+119
-53
lines changed

README.md

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# [WIP] CLI Testing Tool
1+
# CLI Testing Tool
22

33
A testing library that allows you to test input and outputs of your CLI command.
44

5-
*Note: This is a work in progress. The API is likely going to change.*
5+
*Note: This is a work in progress.*
66

77
**Terminal Text Parsing Support Checklist**
88
Refer to [Full List of Ansi Escape Codes](https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797) that need to be handled.
@@ -32,61 +32,80 @@ yarn add --dev cli-testing-tool
3232

3333
Check out [Interactive Examples on Stackblitz](https://stackblitz.com/edit/node-kfod5b?file=examples%2Fprompts%2Fprompts.test.js)
3434

35-
### Eg 1: Hello World Example
35+
### Testing Colored Terminal Text
36+
37+
Check out [this example of StackBlitz](https://stackblitz.com/edit/node-kfod5b?file=examples%2Fgraphic-hello-world%2Fgraphic-hello-world.test.js)
3638

3739
```js
38-
// hello-world.test.js
40+
// colored-greeting.test.js
3941
const { createCommandInterface } = require('cli-testing-tool');
4042

41-
test('should print greetings', async () => {
42-
const commandInterface = createCommandInterface('node ./hello-world.js', {
43-
cwd: __dirname, // Directory from where you want to run the command
43+
test('should print colored greetings', async () => {
44+
const commandInterface = createCommandInterface('node ./graphic-print.js', {
45+
cwd: __dirname, // considering, the test file is in the same directory as the cli file
4446
});
4547
await commandInterface.type('Saurabh\n');
4648
const terminal = await commandInterface.getOutput();
47-
expect(terminal.stringOutput).toBe("What's your name?Hi, Saurabh!");
49+
50+
// ANSI Escape codes are tokenized into readable text token in tokenizedOutput
51+
// Helpful when libraries like inquirer or prompts add ansi-escape codes.
52+
expect(terminal.tokenizedOutput).toBe(
53+
"What's your name?Hi, [BOLD_START][RED_START]Saurabh[COLOR_END][BOLD_END]!"
54+
);
55+
56+
// ANSI Escape codes are not tokenized.
57+
expect(terminal.rawOutput).toBe(
58+
`What's your name?Hi, \x1B[1m\x1B[31mSaurabh\x1B[39m\x1B[22m!`
59+
);
60+
61+
// ANSI Escape codes are removed
62+
expect(terminal.stringOutput).toBe(`What's your name?Hi, Saurabh!`);
4863
});
64+
4965
```
5066

51-
The code that we're testing-
67+
<details>
68+
<summary>Code of the CLI that we're testing in above snippet</summary>
69+
5270
```js
53-
// hello-world.js
71+
// colored-greeting.js
5472
const readline = require('readline').createInterface({
5573
input: process.stdin,
56-
output: process.stdout,
74+
output: process.stdout
5775
});
5876

77+
const bold = (str) => `\x1b[1m${str}\x1b[22m`;
78+
const red = (str) => `\x1b[31m${str}\x1b[39m`;
79+
5980
readline.question(`What's your name?`, (name) => {
60-
console.log(`Hi, ${name}!`);
81+
console.log(`Hi, ${bold(red('Saurabh'))}!`);
6182
readline.close();
6283
});
6384

6485
```
6586

66-
### Eg 2: Tokenized Output
87+
</details>
6788

68-
Check out [this example of StackBlitz](https://stackblitz.com/edit/node-kfod5b?file=examples%2Fcolored-output%2Fcolored-output.test.js)
6989

70-
Sometimes you may want to test if the output has correct color and graphics. You can use the `.tokenizedOutput` method to get tokens in the output.
7190

72-
Check out [list of tokens](https://github.com/saurabhdaware/cli-testing-tool/blob/18e1e12d86cec7b429f949cdd571b13b64fd4747/lib/cli-ansi-parser.js#L28) that library outputs.
91+
## Options
7392

74-
```js
75-
// colored-output.test.js
76-
const { createCommandInterface } = require('cli-testing-tool');
93+
You can pass options as 2nd param to `createCommandInterface`.
7794

78-
test('should have bold red text', async () => {
79-
const commandInterface = createCommandInterface('node ./colored-output.js', {
80-
cwd: __dirname,
81-
});
82-
const terminal = await commandInterface.getOutput();
83-
expect(terminal.tokenizedOutput).toBe("This has a [BOLD_START][RED_START]red and bold[COLOR_END][BOLD_END] text.");
84-
});
95+
The default options are:
96+
```js
97+
const defaultOptions = {
98+
typeDelay: 100, // number. delay between each `.type()` call
99+
logData: false, // boolean. if true, logs the command data on terminal
100+
logError: true, // boolean. if false, won't add command errors on terminal
101+
cwd: process.cwd(), // string. working directory from where your simulated command is executed
102+
env: undefined // object | undefined. environment variables object if there are any
103+
};
85104
```
86105

87-
[More Examples on Stackblitz](https://stackblitz.com/edit/node-kfod5b?file=examples%2Fprompts%2Fprompts.test.js)
88-
106+
----
89107

90108
Big Shoutout to
91109
- [@fnky](https://github.com/fnky) for the [list of all ansi escape codes](https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797).
92-
- [@netzkolchose](https://github.com/netzkolchose) for [node-ansiterminal](https://github.com/netzkolchose/node-ansiterminal) library.
110+
111+
- [@netzkolchose](https://github.com/netzkolchose) for [node-ansiterminal](https://github.com/netzkolchose/node-ansiterminal) library.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const path = require('path');
2+
const { createCommandInterface } = require('../../lib/index');
3+
4+
test('should print greetings', async () => {
5+
const commandInterface = createCommandInterface('node ./graphic-print.js', {
6+
cwd: path.join(__dirname, '..')
7+
});
8+
await commandInterface.type('Saurabh\n');
9+
const terminal = await commandInterface.getOutput();
10+
11+
// ANSI Escape codes are tokenized into readable text token in tokenizedOutput
12+
// Helpful when libraries like inquirer or prompts add ansi-escape codes.
13+
expect(terminal.tokenizedOutput).toBe(
14+
"What's your name?Hi, [BOLD_START][RED_START]Saurabh[COLOR_END][BOLD_END]!"
15+
);
16+
17+
// ANSI Escape codes are not tokenized.
18+
expect(terminal.rawOutput).toBe(
19+
`What's your name?Hi, \x1B[1m\x1B[31mSaurabh\x1B[39m\x1B[22m!`
20+
);
21+
22+
// ANSI Escape codes are removed
23+
expect(terminal.stringOutput).toBe(`What's your name?Hi, Saurabh!`);
24+
});

cli-examples/graphic-print.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const readline = require('readline').createInterface({
2+
input: process.stdin,
3+
output: process.stdout
4+
});
5+
6+
const bold = (str) => `\x1b[1m${str}\x1b[22m`;
7+
const red = (str) => `\x1b[31m${str}\x1b[39m`;
8+
9+
readline.question(`What's your name?`, (name) => {
10+
console.log(`Hi, ${bold(red('Saurabh'))}!`);
11+
readline.close();
12+
});

lib/cli-ansi-parser.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,6 @@
11
const AnsiTerminal = require('node-ansiterminal').AnsiTerminal;
22
const AnsiParser = require('node-ansiparser');
33

4-
// types
5-
/**
6-
* @typedef {({
7-
* stringOutput: string,
8-
* tokenizedOutput: string,
9-
* rawOutput: string
10-
* })} ParsedOutput
11-
*
12-
* @typedef {({
13-
* typeDelay: number,
14-
* logData: boolean,
15-
* cwd: string,
16-
* env: any
17-
* })} Options
18-
*/
19-
204
// Big shoutout to https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 for super cool reference to all ansi codes
215
const ESC = '(?:\\x1[bB])|(?:\\u001b)\\[';
226
const ESC_NO_BRACKET = ESC.replace('\\[', '');

lib/cli-testing-tool.js

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,46 @@ const { parseOutput, STRING_ESC } = require('./cli-ansi-parser');
33

44
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
55

6+
// types
7+
/**
8+
* @typedef {({
9+
* stringOutput: string,
10+
* tokenizedOutput: string,
11+
* rawOutput: string
12+
* })} ParsedOutput
13+
*
14+
* @typedef {({
15+
* typeDelay: number,
16+
* logData: boolean,
17+
* logError: boolean,
18+
* cwd: string,
19+
* env: any
20+
* })} Options
21+
*/
22+
23+
/** @type {Options} defaultOptions */
24+
const defaultOptions = {
25+
typeDelay: 100,
26+
logData: false,
27+
logError: true,
28+
cwd: process.cwd(),
29+
env: undefined
30+
};
31+
632
/**
733
*
834
* @param {string} commandString
9-
* @param {Options} options
35+
* @param {Options} userOptions
1036
*/
11-
const createCommandInterface = (commandString, options) => {
12-
// /** @type {import('child_process').ChildProcessWithoutNullStreams} */
13-
// let command;
37+
const createCommandInterface = (commandString, userOptions = {}) => {
38+
const options = { ...defaultOptions, ...userOptions };
39+
1440
const commandArgs = commandString.split(' ');
1541
const command = spawn(commandArgs[0], commandArgs.slice(1), {
1642
detached: true,
1743
stdio: 'pipe',
18-
cwd: options.cwd || process.cwd(),
19-
env: options.env || undefined
44+
cwd: options.cwd,
45+
env: options.env
2046
});
2147

2248
let outputs = '';
@@ -48,7 +74,7 @@ const createCommandInterface = (commandString, options) => {
4874
);
4975
}
5076

51-
await wait(options.typeDelay ? options.typeDelay : 100);
77+
await wait(options.typeDelay);
5278

5379
return new Promise((resolve) => {
5480
command.stdin.write(`${text}`, () => {
@@ -64,7 +90,7 @@ const createCommandInterface = (commandString, options) => {
6490
};
6591

6692
/**
67-
* @returns {Promise<import('./cli-ansi-parser').ParsedOutput>}
93+
* @returns {Promise<ParsedOutput>}
6894
*/
6995
const getOutput = () => {
7096
if (!isFinishTypingCalled) {

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)