UNPKG

@inquirer/testing

Version:
227 lines (160 loc) 7.45 kB
# `@inquirer/testing` The `@inquirer/testing` package is Inquirer's answer to testing prompts [built with `@inquirer/core`](https://github.com/SBoudrias/Inquirer.js/tree/main/packages/core). # Installation <table> <tr> <th>npm</th> <th>yarn</th> </tr> <tr> <td> ```sh npm install @inquirer/testing --save-dev ``` </td> <td> ```sh yarn add @inquirer/testing --dev ``` </td> </tr> </table> # Usage This package provides two ways to test Inquirer prompts: 1. **Unit testing** with `render()` - Test individual prompts in isolation 2. **E2E testing** with `screen` - Test full CLI applications that use Inquirer ## Unit Testing with `render()` The `render()` function creates and instruments a command line interface for testing a single prompt. ```ts import { render } from '@inquirer/testing'; import input from '@inquirer/input'; describe('input prompt', () => { it('handle simple use case', async () => { const { answer, events, getScreen } = await render(input, { message: 'What is your name', }); expect(getScreen()).toMatchInlineSnapshot(`"? What is your name"`); events.type('J'); expect(getScreen()).toMatchInlineSnapshot(`"? What is your name J"`); events.type('ohn'); events.keypress('enter'); await expect(answer).resolves.toEqual('John'); expect(getScreen()).toMatchInlineSnapshot(`"? What is your name John"`); }); }); ``` ### `render()` API `render` takes 2 arguments: 1. The Inquirer prompt to test (the return value of `createPrompt()`) 2. The prompt configuration (the first prompt argument) `render` returns a promise that resolves once the prompt is rendered. This promise returns: - `answer` (`Promise`) - Resolves when an answer is provided and valid - `getScreen` (`({ raw?: boolean }) => string`) - Returns the current screen content. By default strips ANSI codes - `nextRender` (`() => Promise<void>`) - Wait for the next screen update. Use after triggering async actions (e.g. pressing enter with validation). Coalesces rapid back-to-back renders so a single `await nextRender()` captures the final settled state - `events` - Utilities to interact with the prompt: - `keypress(key: string | KeyObject)` - Trigger a keypress event - `type(text: string)` - Type text into the prompt - `getFullOutput` (`() => Promise<string>`) - Returns the full output interpreted through a virtual terminal, resolving ANSI escape sequences into the actual screen state ### Async actions and `nextRender()` When a keypress triggers an asynchronous action (such as input validation), the screen won't update synchronously. Use `nextRender()` to wait for the prompt to settle before reading the screen: ```ts import { render } from '@inquirer/testing'; import input from '@inquirer/input'; it('shows a validation error', async () => { const { answer, events, getScreen, nextRender } = await render(input, { message: 'Enter a number', validate: (value) => /^\d+$/.test(value) || 'Must be a number', }); events.type('abc'); events.keypress('enter'); await nextRender(); // wait for validation to complete and the error to render expect(getScreen()).toContain('Must be a number'); events.keypress('backspace'); events.keypress('backspace'); events.keypress('backspace'); events.type('42'); events.keypress('enter'); await expect(answer).resolves.toEqual('42'); }); ``` ### Unit Testing Example You can refer to the [`@inquirer/input` test suite](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/input/input.test.ts) for a comprehensive unit testing example using `render()`. ## E2E Testing with `screen` For testing full CLI applications that use Inquirer prompts internally, use the framework-specific entry points: ### Vitest ```ts import { describe, it, expect } from 'vitest'; import { screen } from '@inquirer/testing/vitest'; // Import your CLI AFTER @inquirer/testing/vitest import { runMyCli } from './my-cli.js'; describe('my CLI', () => { it('asks for name and confirms', async () => { const result = runMyCli(); // First prompt is immediately available expect(screen.getScreen()).toContain('What is your name?'); screen.type('John'); screen.keypress('enter'); // Wait for next prompt await screen.next(); expect(screen.getScreen()).toContain('Confirm?'); screen.keypress('enter'); await result; }); }); ``` ### Jest ```ts import { screen } from '@inquirer/testing/jest'; import { runMyCli } from './my-cli.js'; describe('my CLI', () => { it('asks for name and confirms', async () => { const result = runMyCli(); // First prompt is immediately available expect(screen.getScreen()).toContain('What is your name?'); screen.type('John'); screen.keypress('enter'); // Wait for next prompt await screen.next(); expect(screen.getScreen()).toContain('Confirm?'); screen.keypress('enter'); await result; }); }); ``` ### `screen` API The `screen` object provides: - `next()` - Wait for the next screen update (prompt transitions, validation errors, async updates). The initial prompt render is available immediately via `getScreen()` — no `next()` needed - `getScreen({ raw?: boolean })` - Get the current prompt screen content. By default strips ANSI codes - `getFullOutput({ raw?: boolean })` - Get all accumulated output interpreted through a virtual terminal (returns a `Promise`). By default resolves ANSI escape sequences into actual screen state - `type(text)` - Type text (writes to stream AND emits keypresses) - `keypress(key)` - Send a keypress event - `clear()` - Reset screen state (called automatically before each test) ### Mocking Third-Party Prompts All `@inquirer/*` prompts are mocked automatically. To mock a third-party or custom prompt package, use `wrapPrompt` in your own mock call: #### Vitest ```ts import { screen, wrapPrompt } from '@inquirer/testing/vitest'; vi.mock('@my-company/custom-prompt', async (importOriginal) => { const actual = await importOriginal<typeof import('@my-company/custom-prompt')>(); return { ...actual, default: wrapPrompt(actual.default) }; }); ``` #### Jest In Jest, `jest.mock()` factories are hoisted before imports, so `wrapPrompt` must be accessed via `jest.requireActual()` inside the factory: ```ts import { screen } from '@inquirer/testing/jest'; jest.mock('@my-company/custom-prompt', () => { const { wrapPrompt } = jest.requireActual('@inquirer/testing/jest'); const actual = jest.requireActual('@my-company/custom-prompt'); return { ...actual, default: wrapPrompt(actual.default) }; }); ``` ### Important Notes 1. **Import order matters**: Import `@inquirer/testing/vitest` or `@inquirer/testing/jest` BEFORE importing modules that use Inquirer prompts 2. **Editor prompt**: The external editor is mocked — `screen.type()` buffers text, and `screen.keypress('enter')` submits it (same pattern as other prompts). Works with both `waitForUserInput: true` and `false` 3. **Sequential prompts**: Multiple prompts are supported, but they must run sequentially (not concurrently) ### E2E Testing Example You can refer to the [`@inquirer/demo` test suite](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/demo/demo.test.ts) for a comprehensive E2E testing example using `screen`. # License Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/> Licensed under the MIT license.