3.0.0-alpha.3 • Published 3 years ago

clifford v3.0.0-alpha.3

Weekly downloads
4
License
MIT
Repository
github
Last release
3 years ago

Clifford

Simple CLI Integration testing

import clifford from 'clifford'

describe('my cli', () => {
  it('shows help', async () => {
    const cli = clifford('src/index.ts', ['--help'])

    const helpText = await cli.read()
    expect(helpText).toMatchInlineSnapshot(`
      "Usage: testcli

      Commands:
        testcli sure  A nice test command

      Options:
        --version  Show version number                                       [boolean]
        --help     Show help                                                 [boolean]
      "
    `)
  })

  it('reads line by line and input data', async () => {
    const cli = clifford('src/index.ts', ['sure'])

    const firstLine = await cli.readLine()
    expect(firstLine).toEqual('Do you want to see the second line?')

    await cli.type('yeah, sure')

    const secondLine = await cli.readLine()
    expect(secondLine).toEqual('Welcome to the second line')
  })
})

Installing

yarn add --dev clifford

or

npm install --save-dev clifford

API

Initialization

clifford(binPath, args?, options?)

import clifford from 'clifford'

const cli = clifford('./path/to/your/entrypoint.js')

binPath: string

Typedefault
stringrequired

A relative path to the cwd to the entry point of your project. This will be fed to babel-node respecting your local .babelrc if you have one, so you don't need to worry to build your TS before running your tests.

If you don't want to rely on where the tests will be run, you can pass a relative import through require.resolve like so:

const commandPath = require.resolve('./index.ts')
const cli = clifford(commandPath)

args

Typedefault
Array<string>[]

An array with the parameters to be passed to the cli. If you want to run it with --help, for example, you would do like so:

const cli = clifford('src/index.ts', ['--help'])

If you're familiar with child_process usage, this is similar to the second parameter of child_process.spawn.

Be mindful to always split your arguments when you would introduce a space.

// equivalent to ./index.ts --extensions .ts
const cli = clifford('src/index.ts', ['--extensions', '.ts'])

options

Options that modify internal behaviour.

debug
Typedefault
booleanfalse

This option will print out every stdout that the command receives in addition to piping it to the read methods.

useBabelNode
Typedefault
booleantrue if .babelrc is present, or if you point to a non-js file

Whether clifford should use babel-node to run your cli. Provide it as true if your command parameter points to .ts file, or if you want to provide some kind of transpilation step.

When false, clifford will run your cli with the node available in $PATH.

Usage

Clifford will give you a clifford instance with the following methods:

cli.read

read: () -> Promise<string>

it('prints help', async () => {
  const cli = clifford('src/index.ts', ['--help'])
  const output = await cli.read()
  expect(output).toContain('Whatever your help prints')
})

cli.read reads the process' output until its exit event.

Be mindful that if your cli hangs for any reason (e.g. waits for user input) this method will timeout, since it will wait for the process end.

cli.readLine

readLine: () -> Promise<string>

it('prints help gradually', async () => {
  const cli = clifford('src/index.ts', ['--help'])

  const firstLine = await cli.readLine()
  expect(firstLine).toEqual('My first line of content')

  const secondLine = await cli.readLine()
  expect(secondLine).toEqual('My second line of content')
})

cli.readLine returns the next line printed in the screen. In case there's no line to be read in the screen, it will wait until a new one has been printed.

cli.findByText

findByText: (matcher: string | RegExp) => Promise<string>

it('prints the second line eventually', async () => {
  const cli = clifford('src/index.ts', ['--help'])

  const secondLine = await cli.findByText(/second line/)
  expect(secondLine).toEqual('My second line of content')
})

cli.findByText finds a line in the screen and returns it. It will return the first line it finds, including lines that have already been read. In case no line in the current screen satisfies the provided matcher, it will wait until something that is printed does.

cli.waitUntil

waitUntil: (matcher: string | RegExp) -> Promise<string>

it('prints a message before closing', async () => {
  const cli = clifford('src/index.ts', ['--help'])

  const lastLine = await waitUntil('a message')
  expect(lastLine).toEqual("I've sent a message")
})

cli.waitUntil waits util a line satisfies the matcher provided. It won't look at lines that have already been read, so use it only if you're sure that the line you're looking for is not already flushed to the screen. If you want to match already read lines, use findByText.

cli.type

type: (messageToType: string) -> Promise<void>

it('prompts if user wants to continue', async () => {
  const cli = clifford('src/index.ts', ['--help', '--prompt'])

  const firstLine = await cli.readLine()
  expect(firstLine).toEqual('Do you want to read the next line?')

  await cli.type('yes')

  const secondLine = await cli.readLine()
  expect(secondeLine).toEqual('Welcome to the second line')
})

cli.type types a string to the process. This will ultimately write the string provided to the process' stdin feed.

cli.kill

kill: () -> Promise<void>

it('does something and never finishes', async () => {
  const cli = clifford('src/infiniteLoop.js')

  //...

  await cli.kill()
})

cli.kill kills the process and waits until its streams are properly closed. It's advised you wait for this method at the end of tests that don't go through the process until it self-closes.

cli.untilClose

untilClose: () -> Promise<void>

it('takes kind of long to close', () => {
  const cli = clifford('src/moveStuffInADatabase.js')

  // ...

  await cli.untilClose()
})

cli.untilClose will wait until the underlying process has closed. It's effectively the same as cli.kill except it won't trigger the closing of the process itself.

Common issues

babel-node spawn ENOENT

You either provided a file that doesn't exist to clifford, or babel-node is not in your path. You can provide useBabelNode as false, or install the peerDependencies:

yarn add --dev @babel/core @babel/node

If you already have useBabelNode as false, try console.log(cli) to check which path the cli is receiving.

Jest did not exit one second after the test run has completed

This usually means your underlying process has been left hanging in one of your test cases. Try adding cli.untilClose to the end of your tests and see which one times out, so you can properly cli.kill it.

3.0.0-alpha.1

3 years ago

3.0.0-alpha.3

3 years ago

3.0.0-alpha.2

3 years ago

2.0.1

4 years ago

2.0.0

4 years ago

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago

0.1.4

4 years ago

0.1.3

4 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago