import { ReactElement } from 'react'; import { Instance } from 'ink'; import { Writable } from 'stream'; /** * Headless rendering utilities for TermUI components. * * Uses Ink's render() with a mock stdout stream to capture terminal output * as a plain string, enabling unit testing without a real terminal. * * @example * ```tsx * import { renderToString } from '@termui/testing'; * import { Spinner } from '@termui/components'; * * const output = await renderToString(); * expect(output).toContain('⠋'); // first frame * ``` */ interface RenderResult { /** Last rendered output with ANSI codes stripped */ output: string; /** Last rendered output with ANSI codes preserved */ rawOutput: string; /** Re-read the current output (in case state updated) */ rerender(): RenderResult; /** Unmount the component */ unmount(): void; /** The underlying Ink instance */ instance: Instance; } interface TestRenderer { /** Render an element and return the result */ render(element: ReactElement): RenderResult; /** Clean up */ cleanup(): void; } /** * Render a React element to a string synchronously (one frame). * * Use this for quick snapshot-style tests. */ declare function renderToString(element: ReactElement, options?: { cols?: number; rows?: number; waitMs?: number; }): Promise; /** * Create a reusable test renderer. Useful when you need to interact with * the component across multiple assertions. * * @example * ```tsx * const { render, cleanup } = createTestRenderer(); * const result = render(); * expect(result.output).toContain('Hello'); * cleanup(); * ``` */ declare function createTestRenderer(options?: { cols?: number; rows?: number; }): TestRenderer; /** * Query helpers — inspired by @testing-library/dom but for terminal output. * * All queries operate on a plain string (stripped of ANSI codes). */ interface ScreenQueries { /** Returns the first line containing the text, throws if not found */ getByText(text: string | RegExp, output: string): string; /** Returns the first line containing the text, or null */ queryByText(text: string | RegExp, output: string): string | null; /** Returns all lines containing the text */ getAllByText(text: string | RegExp, output: string): string[]; /** Returns true if any line contains the text */ hasText(text: string | RegExp, output: string): boolean; /** Returns the number of lines containing the text */ countByText(text: string | RegExp, output: string): number; /** Returns output split into trimmed non-empty lines */ getLines(output: string): string[]; } declare const screen: ScreenQueries; /** * Keyboard event simulation for TermUI components. * * Writes raw ANSI escape sequences to the process stdin mock so that * Ink's useInput / readline handlers receive the event. */ interface FireEventOptions { /** Custom stdin stream to write to (defaults to process.stdin) */ stdin?: Writable; } /** Map of named keys to their ANSI sequences */ declare const KEY_SEQUENCES: Record; interface FireEventAPI { /** Fire a named key (e.g. 'up', 'enter', 'escape') */ key(name: keyof typeof KEY_SEQUENCES | string, opts?: FireEventOptions): void; /** Type a string character by character */ type(text: string, opts?: FireEventOptions): void; /** Press a single character */ press(char: string, opts?: FireEventOptions): void; } declare const fireEvent: FireEventAPI; /** * waitFor — poll an assertion until it passes or times out. * * @example * ```ts * await waitFor(() => expect(result.rerender().output).toContain('Done')); * ``` */ interface WaitForOptions { /** Maximum time to wait in ms (default: 1000) */ timeout?: number; /** Polling interval in ms (default: 50) */ interval?: number; } /** * Repeatedly calls `fn` until it doesn't throw, or until timeout. * Useful for testing async state transitions in components. */ declare function waitFor(fn: () => void | Promise, options?: WaitForOptions): Promise; /** * Strip volatile content from output to produce a stable snapshot. * * Replaces: * - Spinner animation frames → "[SPINNER]" * - Timestamps → "[TIME]" * - Blinking cursors → "[CURSOR]" */ declare function stripVolatile(output: string): string; /** * Normalize ANSI escape codes to collapse equivalent sequences. * Strips all ANSI, then re-applies consistent formatting. */ declare function normalizeAnsi(output: string): string; /** * Write or compare a snapshot file. * * @param output - current output string (stripped of ANSI) * @param snapshotPath - absolute path to `.snap` file * @returns true if matches (or was written), throws if mismatch */ declare function toMatchSnapshot(output: string, snapshotPath: string): true; /** * Update snapshot file. Call when UPDATE_SNAPSHOTS=1 is set. */ declare function updateSnapshot(output: string, snapshotPath: string): void; interface TerminalMatchers { toMatchTerminalSnapshot(testFilePath?: string, snapshotName?: string): R; } /** * Register custom matchers with Vitest's expect. */ declare function setupTerminalMatchers(expect: { extend: (matchers: object) => void; }): void; /** * CLI testing utilities for TermUI apps. * * @example * ```ts * import { testCLI, mockRegistry, mockFS } from '@termui/testing'; * * const result = await testCLI('npx termui list'); * expect(result.exitCode).toBe(0); * expect(result.stdout).toContain('spinner'); * ``` */ interface CLIResult { stdout: string; stderr: string; exitCode: number; /** stdout with ANSI codes stripped */ output: string; } interface TestCLIOptions { /** Working directory for the CLI process (default: temp dir) */ cwd?: string; /** Environment variables to set */ env?: Record; /** Timeout in milliseconds (default: 15000) */ timeout?: number; /** Stdin to feed to the process */ stdin?: string; } /** * Run a CLI command in a subprocess and capture stdout/stderr/exitCode. * * @param command - Full command string e.g. 'node dist/cli.js list' * @param options - Options for the subprocess */ declare function testCLI(command: string, options?: TestCLIOptions): Promise; interface MockRegistryComponent { name: string; description?: string; version?: string; category?: string; files?: string[]; deps?: string[]; peerComponents?: string[]; /** Source content for each file. Key = filename, value = source string */ source?: Record; } interface MockRegistryHandle { /** Base URL of the mock registry (file:// URL) */ url: string; /** Tear down the mock registry directory */ cleanup(): void; } /** * Create a temporary local registry for testing `npx termui add`. * * Writes a `schema.json` + individual component meta.json + source files * to a temp directory. Pass `handle.url` as the registry URL. * * @example * ```ts * const registry = mockRegistry([ * { name: 'spinner', category: 'feedback', files: ['Spinner.tsx'], * source: { 'Spinner.tsx': 'export function Spinner() { return null; }' } } * ]); * const fs = mockFS({ * 'package.json': JSON.stringify({ name: 'my-app', type: 'module' }), * 'termui.config.json': JSON.stringify({ componentsDir: './components/ui', registry: registry.url }), * }); * const result = await testCLI(`node dist/cli.js add spinner`, { cwd: fs.root }); * registry.cleanup(); * fs.cleanup(); * ``` */ declare function mockRegistry(components: MockRegistryComponent[]): MockRegistryHandle; interface MockFSHandle { /** Root directory of the mock filesystem */ root: string; /** Write a file to the mock filesystem */ writeFile(path: string, content: string): void; /** Read a file from the mock filesystem */ readFile(path: string): string | null; /** Check if a path exists */ exists(path: string): boolean; /** Tear down the mock filesystem */ cleanup(): void; } /** * Create a temporary filesystem sandbox for testing `termui init` / `termui add`. * * Use `handle.root` as the `cwd` for `testCLI`. * * @example * ```ts * const fs = mockFS({ * 'package.json': JSON.stringify({ name: 'my-app', type: 'module' }) * }); * const result = await testCLI('node dist/cli.js init', { cwd: fs.root }); * expect(fs.exists('termui.config.ts')).toBe(true); * fs.cleanup(); * ``` */ declare function mockFS(files?: Record): MockFSHandle; export { type CLIResult, type FireEventOptions, type MockFSHandle, type MockRegistryComponent, type MockRegistryHandle, type RenderResult, type TerminalMatchers, type TestCLIOptions, type TestRenderer, type WaitForOptions, createTestRenderer, fireEvent, mockFS, mockRegistry, normalizeAnsi, renderToString, screen, setupTerminalMatchers, stripVolatile, testCLI, toMatchSnapshot, updateSnapshot, waitFor };