import { CliConfig } from "@sanity/cli-core/types"; import { ClientConfig } from "@sanity/client"; import { CLITelemetryStore } from "@sanity/cli-core/types"; import { Command } from "@oclif/core"; import { Config } from "@oclif/core"; import { Errors } from "@oclif/core"; import { Hook } from "@oclif/core/hooks"; import { Hooks } from "@oclif/core/hooks"; import { Mock } from "vitest"; import nock from "nock"; import { ProjectRootResult } from "@sanity/cli-core/types"; import { SanityClient } from "@sanity/client"; import { SanityCommand } from "@sanity/cli-core/SanityCommand"; import { SpinnerInstance } from "@sanity/cli-core/ux"; import { TestProject } from "vitest/node"; import { vi } from "vitest"; /** @public */ declare interface CaptureOptions { /** * Whether to print the output to the console */ print?: boolean; /** * Whether to strip ANSI escape codes from the output */ stripAnsi?: boolean; testNodeEnv?: string; } /** @public */ declare interface CaptureResult { stderr: string; stdout: string; error?: Error & Partial; result?: T; } declare type CommandClass = (new (argv: string[], config: Config) => Command) & typeof Command; /** * Converts Unix-style paths to platform-appropriate paths. * On Windows: * - Absolute paths starting with '/': adds drive letter and converts to backslashes * - Relative/partial paths: converts forward slashes to backslashes * On Unix: keeps paths as-is. * * @param pathStr - Unix-style path (e.g., '/test/path' or '.config/file.json') * @returns Platform-appropriate path * @internal */ export declare function convertToSystemPath(pathStr: string): string; /** * Creates a mock HTTP server for testing. * * @returns A mock HTTP server object with emit and once methods * @internal */ export declare function createMockHttpServer(): { emit(event: string, ...args: unknown[]): void; once(event: string, listener: (...args: unknown[]) => void): void; }; /** * Creates a mock spinner function to avoid writing output to stdout. * @param overrides - Pass any mocks or stubs for test expectations * @internal * @deprecated Use mocks/cli-core/ux/spinner instead */ export declare function createMockSpinner( overrides?: Partial, ): Mock<(options: string) => SpinnerInstance>; /** * Creates a mock Vite watcher for testing. * * @returns A mock watcher object with add, emit, and on methods * @internal */ export declare function createMockWatcher(): MockWatcher; /** * Creates a real Sanity client instance for testing that makes actual HTTP requests. * Use with mockApi() to intercept and mock the HTTP calls. * * @public * * @example * ```typescript * // Mock getGlobalCliClient to return a test client * vi.mock('@sanity/cli-core', async (importOriginal) => { * const actual = await importOriginal() * const {createTestClient} = await import('@sanity/cli-test') * * return { * ...actual, * getGlobalCliClient: vi.fn().mockImplementation((opts) => { * return Promise.resolve(createTestClient({ * apiVersion: opts.apiVersion, * })) * }), * } * }) * * // Then use mockApi to intercept requests * mockApi({ * apiVersion: 'v2025-02-19', * method: 'get', * uri: '/media-libraries', * }).reply(200, {data: [...]}) * ``` */ export declare function createTestClient(options: CreateTestClientOptions): { client: SanityClient; request: ReturnType; }; /** * Options for createTestClient * * @public */ export declare interface CreateTestClientOptions extends ClientConfig { /** * API version for the client */ apiVersion: string; /** * Authentication token */ token?: string; } /** * Creates a test token for the Sanity CLI * * @public * * @param token - The token to create * @returns void */ export declare function createTestToken(token: string): void; /** * Creates a unique temporary directory for test output. * Uses {@link getTempPath} as the base, creating it if needed. * * @param options - Configuration options: `prefix` (default: 'cli-e2e-') and * `useSystemTmp` (default: false) which uses the OS temp directory instead of * cwd/tmp to avoid monorepo workspace detection by package managers and git. * @returns The path and a cleanup function * @public */ export declare function createTmpDir(options?: { prefix?: string; useSystemTmp?: boolean; }): Promise<{ cleanup: () => Promise; path: string; }>; /** * Default fixtures bundled with the package and their options. * * @public */ export declare const DEFAULT_FIXTURES: Record; /** * @deprecated Use {@link FixtureName} instead. This type alias will be removed in a future release. * @public */ export declare type ExampleName = FixtureName; /** * Valid fixture name type. * @public */ export declare type FixtureName = | "basic-app" | "basic-functions" | "basic-studio" | "federated-studio" | "graphql-studio" | "multi-workspace-studio" | "nextjs-app" | "prebuilt-app" | "prebuilt-studio" | "worst-case-studio"; /** * Options for each fixture. * @public */ export declare interface FixtureOptions { includeDist?: boolean; } /** * Gets the current Windows drive letter from process.cwd(). * Falls back to 'C:\\' if detection fails. * * @returns Drive letter with backslash (e.g., 'C:\\', 'D:\\') or empty string on Unix * @internal */ export declare function getCurrentDrive(): string; /** * Gets the path to the fixtures directory bundled with this package. * * The fixtures are copied during build and bundled with the published package. * This function works the same whether the package is used in a monorepo * or installed from npm. * * @returns Absolute path to the fixtures directory * @internal */ export declare function getFixturesPath(): string; /** * Gets the path to the temporary directory for test fixtures. * * Uses the initial working directory captured when this module was first loaded, * not process.cwd() which may change during test execution. * * @param customTempDir - Optional custom temp directory path * @returns Absolute path to temp directory (default: initial cwd/tmp) * @internal */ export declare function getTempPath(customTempDir?: string): string; /** * Mocks the API calls, add some defaults so it doesn't cause too much friction * * @internal */ export declare function mockApi({ apiHost, apiVersion, includeQueryTag, method, projectId, query, uri, }: MockApiOptions): nock.Interceptor; /** * @internal */ export declare interface MockApiOptions { /** * Uri to mock */ uri: string; /** * Api host to mock, defaults to `https://api.sanity.io` */ apiHost?: string; /** * Api version to mock, defaults to `v2025-05-14` */ apiVersion?: string; /** * Whether to include `tag: 'sanity.cli'` in query parameters. * Defaults to `true`. Set to `false` for endpoints that don't use CLI tagging. */ includeQueryTag?: boolean; /** * HTTP method to mock * * Defaults to 'get' */ method?: "delete" | "get" | "patch" | "post" | "put"; /** * Project ID to mock. When provided, constructs apiHost as `https://{projectId}.api.sanity.io` * Takes precedence over apiHost if both are provided. */ projectId?: string; /** * Query parameters to mock */ query?: Record; } /** * Creates a testable subclass of a command with mocked SanityCommand dependencies. * * @public * * @example * ```ts * // Basic config mocking * const TestAdd = mockSanityCommand(Add, { * cliConfig: { api: { projectId: 'test-project' } } * }) * * // With mock API client * const mockClient = { * getDocument: vi.fn().mockResolvedValue({ _id: 'doc1', title: 'Test' }), * fetch: vi.fn().mockResolvedValue([]), * } * const TestGet = mockSanityCommand(GetDocumentCommand, { * cliConfig: { api: { projectId: 'test-project', dataset: 'production' } }, * projectApiClient: mockClient, * }) * * const {stdout} = await testCommand(TestGet, ['doc1']) * expect(mockClient.getDocument).toHaveBeenCalledWith('doc1') * ``` */ export declare function mockSanityCommand< T extends typeof SanityCommand, >(CommandClass: T, options?: MockSanityCommandOptions): T; /** * @public */ export declare interface MockSanityCommandOptions { /** * Mock CLI config (required if command uses getCliConfig or getProjectId) */ cliConfig?: CliConfig; /** * When provided, getCliConfig() will throw this error instead of returning a config. * Useful for simulating running outside a project directory. */ cliConfigError?: Error; /** * Mock whether the terminal is interactive (used by isUnattended) */ isInteractive?: boolean; /** * Mock project root result (required if command uses getProjectRoot) */ projectRoot?: ProjectRootResult; /** * Mock authentication token (passed to API clients, bypasses getCliToken) */ token?: string; } /** * @public * @param options - Options for mocking the telemetry store. * @returns The mocked telemetry store. */ export declare const mockTelemetry: ( options?: MockTelemetryOptions, ) => CLITelemetryStore; /** * @public */ export declare interface MockTelemetryOptions { trace?: () => void; updateUserProperties?: () => void; } /** * @internal */ export declare interface MockWatcher { add: Mock; emit: (event: string, ...args: unknown[]) => void; on: (event: string, listener: (...args: unknown[]) => void) => void; } declare interface Options { config: Config; Command?: Command.Class; context?: Hook.Context; } /** * Global setup function for initializing test fixtures. * * Copies fixtures from the bundled location to a temp directory * and installs dependencies. * * Note: Fixtures are NOT built during setup. Tests that need built * fixtures should build them as part of the test. * * This function is designed to be used with vitest globalSetup. * * @public * * @param options - Configuration options * @example * ```typescript * // In vitest.config.ts * export default defineConfig({ * test: { * globalSetup: ['@sanity/cli-test/vitest'] * } * }) * ``` */ export declare function setup( _: TestProject, options?: SetupTestFixturesOptions, ): Promise; /** * Options for setupTestFixtures * * @public */ export declare interface SetupTestFixturesOptions { /** * Glob patterns for additional fixture directories to set up. * * Each pattern is matched against directories in the current working directory. * Only directories containing a `package.json` file are included. * * @example * ```typescript * ['fixtures/*', 'dev/*'] * ``` */ additionalFixtures?: string[]; /** * When true, passes `--ignore-workspace` to pnpm install so fixtures get * their own node_modules even when the temp directory is inside a pnpm * workspace. Required for E2E tests that spawn the CLI binary against * fixture directories. */ ignoreWorkspace?: boolean; /** * Custom temp directory path. Defaults to process.cwd()/tmp */ tempDir?: string; } /** * Teardown function to clean up test fixtures. * * Removes the temp directory created by setupTestFixtures. * * This function is designed to be used with vitest globalSetup. * * @public * * @param options - Configuration options */ export declare function teardown( options?: TeardownTestFixturesOptions, ): Promise; /** * Options for teardownTestFixtures * * @public */ export declare interface TeardownTestFixturesOptions { /** * Custom temp directory path. Defaults to process.cwd()/tmp */ tempDir?: string; } /** * @public */ export declare function testCommand( command: CommandClass, args?: string[], options?: TestCommandOptions, ): Promise>; /** * @public */ export declare interface TestCommandOptions { /** * Options for capturing output */ capture?: CaptureOptions; /** * Partial oclif config overrides */ config?: Partial; /** * Mock options for SanityCommand dependencies (config, project root, API clients). * When provided, the command is automatically wrapped with mockSanityCommand. */ mocks?: MockSanityCommandOptions & MockTelemetryOptions; } /** * Recursively copy a directory, skipping specified folders. * * @param srcDir - Source directory to copy from * @param destDir - Destination directory to copy to * @param skip - Array of directory/file names to skip (e.g., ['node_modules', 'dist']) * @internal */ export declare function testCopyDirectory( srcDir: string, destDir: string, skip?: string[], ): Promise; /** * @deprecated Use {@link testFixture} instead. This function will be removed in a future release. * * Clones an example (now called fixture) directory into a temporary directory with an isolated copy. * * @param exampleName - The name of the example/fixture to clone (e.g., 'basic-app', 'basic-studio') * @param options - Configuration options * @returns The absolute path to the temporary directory containing the example/fixture * * @public */ export declare function testExample( exampleName: FixtureName | (string & {}), options?: TestFixtureOptions, ): Promise; /** * @deprecated Use {@link TestFixtureOptions} instead. This type alias will be removed in a future release. * @public */ export declare type TestExampleOptions = TestFixtureOptions; /** * Clones a fixture directory into a temporary directory with an isolated copy. * * The function creates a unique temporary copy of the specified fixture with: * - A random unique ID to avoid conflicts between parallel tests * - Symlinked node_modules for performance (from the global setup version) * - Modified package.json name to prevent conflicts * * The fixture is first looked up in the temp directory (if global setup ran), * otherwise it falls back to the bundled fixtures in the package. * * @param fixtureName - The name of the fixture to clone (e.g., 'basic-app', 'basic-studio') * @param options - Configuration options * @returns The absolute path to the temporary directory containing the fixture * * @public * * @example * ```typescript * import {testFixture} from '@sanity/cli-test' * import {describe, test} from 'vitest' * * describe('my test suite', () => { * test('should work with basic-studio', async () => { * const cwd = await testFixture('basic-studio') * // ... run your tests in this directory * }) * }) * ``` */ export declare function testFixture( fixtureName: FixtureName | (string & {}), options?: TestFixtureOptions, ): Promise; /** * @public */ export declare interface TestFixtureOptions { /** * Custom temp directory. Defaults to process.cwd()/tmp (or the OS temp directory when * `useSystemTmp` is true). */ tempDir?: string; /** * Use the OS temp directory instead of cwd/tmp. Avoids monorepo workspace detection by * package managers and git when tests run inside a monorepo. Ignored when `tempDir` is set. */ useSystemTmp?: boolean; } /** * Test an oclif hook * * @public * * @example * ```ts * const result = await testHook(hook, { * Command: Command.loadable, * context: { * config: Config.load({ * // CLI root is the directory of the package that contains the hook. * root: path.resolve(fileURLToPath(import.meta.url), '../../../root'), * }), * }, * }) * ``` * * @param hook - The hook to test * @param options - The options for the hook * @returns The result of the hook */ export declare function testHook( hook: Hook, options: Options, ): Promise>; export {};