@oclif/test
Advanced tools
Comparing version 4.0.1-beta.2 to 4.0.1-beta.3
@@ -6,3 +6,3 @@ import { Errors, Interfaces } from '@oclif/core'; | ||
}; | ||
export declare function captureOutput<T>(fn: () => Promise<unknown>, opts?: CaptureOptions): Promise<{ | ||
type CaptureResult<T> = { | ||
error?: Error & Partial<Errors.CLIError>; | ||
@@ -12,15 +12,45 @@ result?: T; | ||
stdout: string; | ||
}>; | ||
export declare function runCommand<T>(args: string | string[], loadOpts?: Interfaces.LoadOptions, captureOpts?: CaptureOptions): Promise<{ | ||
error?: Error & Partial<Errors.CLIError>; | ||
result?: T; | ||
stderr: string; | ||
stdout: string; | ||
}>; | ||
export declare function runHook<T>(hook: string, options: Record<string, unknown>, loadOpts?: Interfaces.LoadOptions, recordOpts?: CaptureOptions): Promise<{ | ||
error?: Error & Partial<Errors.CLIError>; | ||
result?: T; | ||
stderr: string; | ||
stdout: string; | ||
}>; | ||
}; | ||
/** | ||
* Capture the stderr and stdout output of a function | ||
* @param fn async function to run | ||
* @param opts options | ||
* - print: Whether to print the output to the console | ||
* - stripAnsi: Whether to strip ANSI codes from the output | ||
* @returns {Promise<CaptureResult<T>>} Captured output | ||
* - error: Error object if the function throws an error | ||
* - result: Result of the function if it returns a value and succeeds | ||
* - stderr: Captured stderr output | ||
* - stdout: Captured stdout output | ||
*/ | ||
export declare function captureOutput<T>(fn: () => Promise<unknown>, opts?: CaptureOptions): Promise<CaptureResult<T>>; | ||
/** | ||
* Capture the stderr and stdout output of a command in your CLI | ||
* @param args Command arguments, e.g. `['my:command', '--flag']` or `'my:command --flag'` | ||
* @param loadOpts options for loading oclif `Config` | ||
* @param captureOpts options for capturing the output | ||
* - print: Whether to print the output to the console | ||
* - stripAnsi: Whether to strip ANSI codes from the output | ||
* @returns {Promise<CaptureResult<T>>} Captured output | ||
* - error: Error object if the command throws an error | ||
* - result: Result of the command if it returns a value and succeeds | ||
* - stderr: Captured stderr output | ||
* - stdout: Captured stdout output | ||
*/ | ||
export declare function runCommand<T>(args: string | string[], loadOpts?: Interfaces.LoadOptions, captureOpts?: CaptureOptions): Promise<CaptureResult<T>>; | ||
/** | ||
* Capture the stderr and stdout output of a hook in your CLI | ||
* @param hook Hook name | ||
* @param options options to pass to the hook | ||
* @param loadOpts options for loading oclif `Config` | ||
* @param captureOpts options for capturing the output | ||
* - print: Whether to print the output to the console | ||
* - stripAnsi: Whether to strip ANSI codes from the output | ||
* @returns {Promise<CaptureResult<T>>} Captured output | ||
* - error: Error object if the hook throws an error | ||
* - result: Result of the hook if it returns a value and succeeds | ||
* - stderr: Captured stderr output | ||
* - stdout: Captured stdout output | ||
*/ | ||
export declare function runHook<T>(hook: string, options: Record<string, unknown>, loadOpts?: Interfaces.LoadOptions, captureOpts?: CaptureOptions): Promise<CaptureResult<T>>; | ||
export {}; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -8,3 +31,2 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
const core_1 = require("@oclif/core"); | ||
const ansis_1 = __importDefault(require("ansis")); | ||
const debug_1 = __importDefault(require("debug")); | ||
@@ -31,5 +53,16 @@ const node_path_1 = require("node:path"); | ||
} | ||
/** | ||
* Capture the stderr and stdout output of a function | ||
* @param fn async function to run | ||
* @param opts options | ||
* - print: Whether to print the output to the console | ||
* - stripAnsi: Whether to strip ANSI codes from the output | ||
* @returns {Promise<CaptureResult<T>>} Captured output | ||
* - error: Error object if the function throws an error | ||
* - result: Result of the function if it returns a value and succeeds | ||
* - stderr: Captured stderr output | ||
* - stdout: Captured stdout output | ||
*/ | ||
async function captureOutput(fn, opts) { | ||
const print = opts?.print ?? false; | ||
const stripAnsi = opts?.stripAnsi ?? true; | ||
const originals = { | ||
@@ -44,3 +77,4 @@ NODE_ENV: process.env.NODE_ENV, | ||
}; | ||
const toString = (str) => (stripAnsi ? ansis_1.default.strip(str.toString()) : str.toString()); | ||
const { default: stripAnsi } = (opts?.stripAnsi ?? true) ? await Promise.resolve().then(() => __importStar(require('strip-ansi'))) : { default: (str) => str }; | ||
const toString = (str) => stripAnsi(str.toString()); | ||
const getStderr = () => output.stderr.map((b) => toString(b)).join(''); | ||
@@ -87,2 +121,15 @@ const getStdout = () => output.stdout.map((b) => toString(b)).join(''); | ||
exports.captureOutput = captureOutput; | ||
/** | ||
* Capture the stderr and stdout output of a command in your CLI | ||
* @param args Command arguments, e.g. `['my:command', '--flag']` or `'my:command --flag'` | ||
* @param loadOpts options for loading oclif `Config` | ||
* @param captureOpts options for capturing the output | ||
* - print: Whether to print the output to the console | ||
* - stripAnsi: Whether to strip ANSI codes from the output | ||
* @returns {Promise<CaptureResult<T>>} Captured output | ||
* - error: Error object if the command throws an error | ||
* - result: Result of the command if it returns a value and succeeds | ||
* - stderr: Captured stderr output | ||
* - stdout: Captured stdout output | ||
*/ | ||
async function runCommand(args, loadOpts, captureOpts) { | ||
@@ -98,3 +145,17 @@ const loadOptions = makeLoadOptions(loadOpts); | ||
exports.runCommand = runCommand; | ||
async function runHook(hook, options, loadOpts, recordOpts) { | ||
/** | ||
* Capture the stderr and stdout output of a hook in your CLI | ||
* @param hook Hook name | ||
* @param options options to pass to the hook | ||
* @param loadOpts options for loading oclif `Config` | ||
* @param captureOpts options for capturing the output | ||
* - print: Whether to print the output to the console | ||
* - stripAnsi: Whether to strip ANSI codes from the output | ||
* @returns {Promise<CaptureResult<T>>} Captured output | ||
* - error: Error object if the hook throws an error | ||
* - result: Result of the hook if it returns a value and succeeds | ||
* - stderr: Captured stderr output | ||
* - stdout: Captured stdout output | ||
*/ | ||
async function runHook(hook, options, loadOpts, captureOpts) { | ||
const loadOptions = makeLoadOptions(loadOpts); | ||
@@ -105,4 +166,4 @@ debug('loadOpts: %O', loadOptions); | ||
return config.runHook(hook, options); | ||
}, recordOpts); | ||
}, captureOpts); | ||
} | ||
exports.runHook = runHook; |
{ | ||
"name": "@oclif/test", | ||
"description": "test helpers for oclif components", | ||
"version": "4.0.1-beta.2", | ||
"version": "4.0.1-beta.3", | ||
"author": "Salesforce", | ||
"bugs": "https://github.com/oclif/test/issues", | ||
"dependencies": { | ||
"ansis": "^3.2.0", | ||
"debug": "^4.3.4" | ||
"debug": "^4.3.4", | ||
"strip-ansi": "^7.1.0" | ||
}, | ||
"peerDependencies": { | ||
"@oclif/core": "^4.0.0-beta.7" | ||
"@oclif/core": ">= 3.0.0" | ||
}, | ||
@@ -14,0 +14,0 @@ "devDependencies": { |
229
README.md
@@ -9,228 +9,31 @@ # @oclif/test | ||
## Usage | ||
## Migration | ||
`@oclif/test` is an extension of [fancy-test](https://github.com/oclif/fancy-test). Please see the [fancy-test documentation](https://github.com/oclif/fancy-test#fancy-test) for all the features that are available. | ||
See the [V4 Migration Guide](./MIGRATION.md) if you are migrating from v3 or older. | ||
The following are the features that `@oclif/test` adds to `fancy-test`. | ||
## Usage | ||
### `.loadConfig()` | ||
`@oclif/test` provides a handful of utilities that make it easy to test your [oclif](https://oclif.io) CLI. | ||
`.loadConfig()` creates and returns a new [`Config`](https://github.com/oclif/core/blob/main/src/config/config.ts) instance. This instance will be available on the `ctx` variable that's provided in the callback. | ||
### `captureOutput` | ||
```typescript | ||
import {join} from 'node:path' | ||
import {expect, test} from '@oclif/test' | ||
`captureOutput` allows you to get the stdout, stderr, return value, and error of the callback you provide it. This makes it possible to assert that certain strings were printed to stdout and stderr or that the callback failed with the expected error or succeeded with the expected result. | ||
const root = join(__dirname, 'fixtures/test-cli') | ||
test | ||
.loadConfig({root}) | ||
.stdout() | ||
.command(['foo:bar']) | ||
.it('should run the command from the given directory', (ctx) => { | ||
expect(ctx.stdout).to.equal('hello world!\n') | ||
expect(ctx.config.root).to.equal(root) | ||
const {name} = ctx.returned as {name: string} | ||
expect(name).to.equal('world') | ||
}) | ||
``` | ||
**Options** | ||
If you would like to run the same test without using `@oclif/test`: | ||
- `print` - Print everything that goes to stdout and stderr. | ||
- `stripAnsi` - Strip ansi codes from everything that goes to stdout and stderr. Defaults to true. | ||
```typescript | ||
import {Config, ux} from '@oclif/core' | ||
import {expect} from 'chai' | ||
import {join} from 'node:path' | ||
import {SinonSandbox, SinonStub, createSandbox} from 'sinon' | ||
See the [tests](./test/capture-output.test.ts) for example usage. | ||
const root = join(__dirname, 'fixtures/test-cli') | ||
describe('non-fancy test', () => { | ||
let sandbox: SinonSandbox | ||
let config: Config | ||
let stdoutStub: SinonStub | ||
### `runCommand` | ||
beforeEach(async () => { | ||
sandbox = createSandbox() | ||
stdoutStub = sandbox.stub(ux.write, 'stdout') | ||
config = await Config.load({root}) | ||
}) | ||
`runCommand` allows you to get the stdout, stderr, return value, and error of a command in your CLI. | ||
afterEach(async () => { | ||
sandbox.restore() | ||
}) | ||
See the [tests](./test/run-command.test.ts) for example usage. | ||
it('should run command from the given directory', async () => { | ||
const {name} = await config.runCommand<{name: string}>('foo:bar') | ||
expect(stdoutStub.calledWith('hello world!\n')).to.be.true | ||
expect(config.root).to.equal(root) | ||
expect(name).to.equal('world') | ||
}) | ||
}) | ||
``` | ||
### `runHook` | ||
### `.command()` | ||
`runHook` allows you to get the stdout, stderr, return value, and error of a hook in your CLI. | ||
`.command()` let's you run a command from your CLI. | ||
```typescript | ||
import {expect, test} from '@oclif/test' | ||
describe('hello world', () => { | ||
test | ||
.stdout() | ||
.command(['hello:world']) | ||
.it('runs hello world cmd', (ctx) => { | ||
expect(ctx.stdout).to.contain('hello world!') | ||
}) | ||
}) | ||
``` | ||
For a [single command cli](https://oclif.io/docs/single_command_cli) you would provide `'.'` as the command. For instance: | ||
```typescript | ||
import {expect, test} from '@oclif/test' | ||
describe('hello world', () => { | ||
test | ||
.stdout() | ||
.command(['.']) | ||
.it('runs hello world cmd', (ctx) => { | ||
expect(ctx.stdout).to.contain('hello world!') | ||
}) | ||
}) | ||
``` | ||
If you would like to run the same test without using `@oclif/test`: | ||
```typescript | ||
import {Config, ux} from '@oclif/core' | ||
import {expect} from 'chai' | ||
import {SinonSandbox, SinonStub, createSandbox} from 'sinon' | ||
describe('non-fancy test', () => { | ||
let sandbox: SinonSandbox | ||
let config: Config | ||
let stdoutStub: SinonStub | ||
beforeEach(async () => { | ||
sandbox = createSandbox() | ||
stdoutStub = sandbox.stub(ux.write, 'stdout') | ||
config = await Config.load({root: process.cwd()}) | ||
}) | ||
afterEach(async () => { | ||
sandbox.restore() | ||
}) | ||
it('should run command', async () => { | ||
// use '.' for a single command CLI | ||
const {name} = await config.runCommand<{name: string}>('hello:world') | ||
expect(stdoutStub.calledWith('hello world!\n')).to.be.true | ||
expect(name).to.equal('world') | ||
}) | ||
}) | ||
``` | ||
### `.exit()` | ||
`.exit()` let's you test that a command exited with a certain exit code. | ||
```typescript | ||
import {join} from 'node:path' | ||
import {expect, test} from '@oclif/test' | ||
describe('exit', () => { | ||
test | ||
.loadConfig() | ||
.stdout() | ||
.command(['hello:world', '--code=101']) | ||
.exit(101) | ||
.do((output) => expect(output.stdout).to.equal('exiting with code 101\n')) | ||
.it('should exit with code 101') | ||
}) | ||
``` | ||
If you would like to run the same test without using `@oclif/test`: | ||
```typescript | ||
import {Config, Errors, ux} from '@oclif/core' | ||
import {expect} from 'chai' | ||
import {SinonSandbox, createSandbox} from 'sinon' | ||
describe('non-fancy test', () => { | ||
let sandbox: SinonSandbox | ||
let config: Config | ||
beforeEach(async () => { | ||
sandbox = createSandbox() | ||
sandbox.stub(ux.write, 'stdout') | ||
config = await Config.load({root: process.cwd()}) | ||
}) | ||
afterEach(async () => { | ||
sandbox.restore() | ||
}) | ||
it('should run command from the given directory', async () => { | ||
try { | ||
await config.runCommand('.') | ||
throw new Error('Expected CLIError to be thrown') | ||
} catch (error) { | ||
if (error instanceof Errors.CLIError) { | ||
expect(error.oclif.exit).to.equal(101) | ||
} else { | ||
throw error | ||
} | ||
} | ||
}) | ||
}) | ||
``` | ||
### `.hook()` | ||
`.hook()` let's you test a hook in your CLI. | ||
```typescript | ||
import {join} from 'node:path' | ||
import {expect, test} from '@oclif/test' | ||
const root = join(__dirname, 'fixtures/test-cli') | ||
describe('hooks', () => { | ||
test | ||
.loadConfig({root}) | ||
.stdout() | ||
.hook('foo', {argv: ['arg']}, {root}) | ||
.do((output) => expect(output.stdout).to.equal('foo hook args: arg\n')) | ||
.it('should run hook') | ||
}) | ||
``` | ||
If you would like to run the same test without using `@oclif/test`: | ||
```typescript | ||
import {Config, ux} from '@oclif/core' | ||
import {expect} from 'chai' | ||
import {SinonSandbox, SinonStub, createSandbox} from 'sinon' | ||
describe('non-fancy test', () => { | ||
let sandbox: SinonSandbox | ||
let config: Config | ||
let stdoutStub: SinonStub | ||
beforeEach(async () => { | ||
sandbox = createSandbox() | ||
stdoutStub = sandbox.stub(ux.write, 'stdout') | ||
config = await Config.load({root: process.cwd()}) | ||
}) | ||
afterEach(async () => { | ||
sandbox.restore() | ||
}) | ||
it('should run hook', async () => { | ||
const {name} = await config.runHook('foo', {argv: ['arg']}) | ||
expect(stdoutStub.calledWith('foo hook args: arg\n')).to.be.true | ||
}) | ||
}) | ||
``` | ||
See the [tests](./test/run-hook.test.ts) for example usage. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
217
13343
39
+ Addedstrip-ansi@^7.1.0
+ Addedansi-regex@6.1.0(transitive)
+ Addedstrip-ansi@7.1.0(transitive)
- Removedansis@^3.2.0