Socket
Socket
Sign inDemoInstall

@oclif/test

Package Overview
Dependencies
Maintainers
3
Versions
155
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@oclif/test - npm Package Compare versions

Comparing version 4.0.1-dev.0 to 4.0.1

58

lib/index.d.ts

@@ -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[], 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 {};

@@ -11,44 +11,3 @@ "use strict";

const node_path_1 = require("node:path");
const debug = (0, debug_1.default)('test');
const RECORD_OPTIONS = {
print: false,
stripAnsi: true,
};
const originals = {
stderr: process.stderr.write,
stdout: process.stdout.write,
};
const output = {
stderr: [],
stdout: [],
};
function mockedStdout(str, encoding, cb) {
output.stdout.push(str);
if (!RECORD_OPTIONS.print)
return true;
if (typeof encoding === 'string') {
return originals.stdout.bind(process.stdout)(str, encoding, cb);
}
return originals.stdout.bind(process.stdout)(str, cb);
}
function mockedStderr(str, encoding, cb) {
output.stderr.push(str);
if (!RECORD_OPTIONS.print)
return true;
if (typeof encoding === 'string') {
return originals.stdout.bind(process.stderr)(str, encoding, cb);
}
return originals.stdout.bind(process.stderr)(str, cb);
}
const restore = () => {
process.stderr.write = originals.stderr;
process.stdout.write = originals.stdout;
};
const reset = () => {
output.stderr = [];
output.stdout = [];
};
const toString = (str) => RECORD_OPTIONS.stripAnsi ? ansis_1.default.strip(str.toString()) : str.toString();
const getStderr = () => output.stderr.map((b) => toString(b)).join('');
const getStdout = () => output.stdout.map((b) => toString(b)).join('');
const debug = (0, debug_1.default)('oclif-test');
function traverseFilePathUntil(filename, predicate) {

@@ -61,14 +20,56 @@ let current = filename;

}
function makeLoadOptions(loadOpts) {
return (loadOpts ?? {
root: traverseFilePathUntil(
function findRoot() {
return (process.env.OCLIF_TEST_ROOT ??
// eslint-disable-next-line unicorn/prefer-module
require.main?.path ?? module.path, (p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn'))),
});
Object.values(require.cache).find((m) => m?.children.includes(module))?.filename ??
traverseFilePathUntil(
// eslint-disable-next-line unicorn/prefer-module
require.main?.path ?? module.path, (p) => !(p.includes('node_modules') || p.includes('.pnpm') || p.includes('.yarn'))));
}
function makeLoadOptions(loadOpts) {
return loadOpts ?? { root: findRoot() };
}
/**
* 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) {
RECORD_OPTIONS.print = opts?.print ?? false;
RECORD_OPTIONS.stripAnsi = opts?.stripAnsi ?? true;
process.stderr.write = mockedStderr;
process.stdout.write = mockedStdout;
const print = opts?.print ?? false;
const stripAnsi = opts?.stripAnsi ?? true;
const originals = {
NODE_ENV: process.env.NODE_ENV,
stderr: process.stderr.write,
stdout: process.stdout.write,
};
const output = {
stderr: [],
stdout: [],
};
const toString = (str) => (stripAnsi ? ansis_1.default.strip(str.toString()) : str.toString());
const getStderr = () => output.stderr.map((b) => toString(b)).join('');
const getStdout = () => output.stdout.map((b) => toString(b)).join('');
const mock = (std) => (str, encoding, cb) => {
output[std].push(str);
if (print) {
if (encoding !== null && typeof encoding === 'function') {
cb = encoding;
encoding = undefined;
}
originals[std].apply(process[std], [str, encoding, cb]);
}
else if (typeof cb === 'function')
cb();
return true;
};
process.stdout.write = mock('stdout');
process.stderr.write = mock('stderr');
process.env.NODE_ENV = 'test';
try {

@@ -84,4 +85,4 @@ const result = await fn();

return {
...(error instanceof core_1.Errors.CLIError && { error }),
...(error instanceof Error && { error }),
...(error instanceof core_1.Errors.CLIError && { error: { ...error, message: toString(error.message) } }),
...(error instanceof Error && { error: { ...error, message: toString(error.message) } }),
stderr: getStderr(),

@@ -92,21 +93,53 @@ stdout: getStdout(),

finally {
restore();
reset();
process.stderr.write = originals.stderr;
process.stdout.write = originals.stdout;
process.env.NODE_ENV = originals.NODE_ENV;
}
}
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) {
const loadOptions = makeLoadOptions(loadOpts);
debug('loadOpts: %O', loadOpts);
return captureOutput(async () => (0, core_1.run)(args, loadOptions), captureOpts);
const argsArray = (Array.isArray(args) ? args : [args]).join(' ').split(' ');
const [id, ...rest] = argsArray;
const finalArgs = id === '.' ? rest : argsArray;
debug('loadOpts: %O', loadOptions);
debug('args: %O', finalArgs);
return captureOutput(async () => (0, core_1.run)(finalArgs, loadOptions), captureOpts);
}
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);
debug('loadOpts: %O', loadOpts);
debug('loadOpts: %O', loadOptions);
return captureOutput(async () => {
const config = await core_1.Config.load(loadOptions);
return config.runHook(hook, options);
}, recordOpts);
}, captureOpts);
}
exports.runHook = runHook;
{
"name": "@oclif/test",
"description": "test helpers for oclif components",
"version": "4.0.1-dev.0",
"version": "4.0.1",
"author": "Salesforce",

@@ -12,3 +12,3 @@ "bugs": "https://github.com/oclif/test/issues",

"peerDependencies": {
"@oclif/core": "^4.0.0-beta.6"
"@oclif/core": ">= 3.0.0"
},

@@ -19,5 +19,7 @@ "devDependencies": {

"@oclif/prettier-config": "^0.2.1",
"@types/chai": "^4.3.16",
"@types/debug": "^4.1.12",
"@types/mocha": "^10",
"@types/node": "^18",
"chai": "^5.1.1",
"commitlint": "^18.6.1",

@@ -33,3 +35,3 @@ "eslint": "^8.57.0",

"shx": "^0.3.3",
"ts-node": "^10.9.2",
"tsx": "^4.10.2",
"typescript": "^5.4.5"

@@ -36,0 +38,0 @@ },

@@ -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.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc