From 2d421a508c9dbde3357e3311cad95f742454c75c Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 2 Dec 2024 17:12:30 +0100 Subject: [PATCH] docs: restructure the sudebar, update TestRunResult type --- docs/.vitepress/config.ts | 84 +++- docs/advanced/api.md | 457 --------------------- docs/advanced/api/index.md | 102 +++++ docs/advanced/api/test-case.md | 136 ++++++ docs/advanced/api/test-collection.md | 50 +++ docs/advanced/api/test-module.md | 46 +++ docs/advanced/api/test-project.md | 276 +++++++++++++ docs/advanced/api/test-specification.md | 3 + docs/advanced/api/test-suite.md | 46 +++ docs/advanced/api/vitest.md | 165 ++++++++ docs/advanced/pool.md | 2 +- docs/advanced/reporters.md | 294 +------------ docs/advanced/runner.md | 2 +- eslint.config.js | 1 + packages/vitest/src/node/cli/cac.ts | 8 +- packages/vitest/src/node/core.ts | 18 +- packages/vitest/src/node/project.ts | 8 +- packages/vitest/src/node/specifications.ts | 6 +- packages/vitest/src/node/types/tests.ts | 4 +- packages/vitest/src/public/node.ts | 4 +- 20 files changed, 922 insertions(+), 790 deletions(-) delete mode 100644 docs/advanced/api.md create mode 100644 docs/advanced/api/index.md create mode 100644 docs/advanced/api/test-case.md create mode 100644 docs/advanced/api/test-collection.md create mode 100644 docs/advanced/api/test-module.md create mode 100644 docs/advanced/api/test-project.md create mode 100644 docs/advanced/api/test-specification.md create mode 100644 docs/advanced/api/test-suite.md create mode 100644 docs/advanced/api/vitest.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 388181400ce4c..fd0479d4f40ea 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -146,7 +146,7 @@ export default ({ mode }: { mode: string }) => { items: [ { text: 'Advanced API', - link: '/advanced/api', + link: '/advanced/api/', activeMatch: '^/advanced/', }, { @@ -243,7 +243,15 @@ export default ({ mode }: { mode: string }) => { }, ], }, - footer(), + { + items: [ + ...footer(), + { + text: 'Node API Reference', + link: '/advanced/api/', + }, + ], + }, ], '/advanced': [ { @@ -251,8 +259,46 @@ export default ({ mode }: { mode: string }) => { collapsed: false, items: [ { - text: 'Vitest Node API', - link: '/advanced/api', + text: 'Node API', + items: [ + { + text: 'Getting Started', + link: '/advanced/api/', + }, + { + text: 'Vitest', + link: '/advanced/api/vitest', + }, + { + text: 'TestProject', + link: '/advanced/api/test-project', + }, + { + text: 'TestSpecification', + link: '/advanced/api/test-specification', + }, + ], + }, + { + text: 'Test Task API', + items: [ + { + text: 'TestCase', + link: '/advanced/api/test-case', + }, + { + text: 'TestSuite', + link: '/advanced/api/test-suite', + }, + { + text: 'TestModule', + link: '/advanced/api/test-module', + }, + { + text: 'TestCollection', + link: '/advanced/api/test-collection', + }, + ], }, { text: 'Runner API', @@ -282,7 +328,9 @@ export default ({ mode }: { mode: string }) => { }, ], }, - footer(), + { + items: footer(), + }, ], '/team': [], '/': [ @@ -308,7 +356,7 @@ export default ({ mode }: { mode: string }) => { link: '/guide/browser', }, { - text: 'Advanced API', + text: 'Node API Reference', link: '/advanced/api', }, { @@ -325,19 +373,17 @@ export default ({ mode }: { mode: string }) => { })) } -function footer(): DefaultTheme.SidebarItem { - return { - items: [ - { - text: 'Config Reference', - link: '/config/', - }, - { - text: 'Test API Reference', - link: '/api/', - }, - ], - } +function footer(): DefaultTheme.SidebarItem[] { + return [ + { + text: 'Config Reference', + link: '/config/', + }, + { + text: 'Test API Reference', + link: '/api/', + }, + ] } function introduction(): DefaultTheme.SidebarItem[] { diff --git a/docs/advanced/api.md b/docs/advanced/api.md deleted file mode 100644 index 505a6972fdd96..0000000000000 --- a/docs/advanced/api.md +++ /dev/null @@ -1,457 +0,0 @@ ---- -outline: [2, 3] ---- - -# Node API - -## startVitest - -You can start running Vitest tests using its Node API: - -```js -import { startVitest } from 'vitest/node' - -const vitest = await startVitest('test') - -await vitest?.close() -``` - -`startVitest` function returns `Vitest` instance if tests can be started. It returns `undefined`, if one of the following occurs: - -- Vitest didn't find the `vite` package (usually installed with Vitest) -- If coverage is enabled and run mode is "test", but the coverage package is not installed (`@vitest/coverage-v8` or `@vitest/coverage-istanbul`) -- If the environment package is not installed (`jsdom`/`happy-dom`/`@edge-runtime/vm`) - -If `undefined` is returned or tests failed during the run, Vitest sets `process.exitCode` to `1`. - -If watch mode is not enabled, Vitest will call `close` method. - -If watch mode is enabled and the terminal supports TTY, Vitest will register console shortcuts. - -You can pass down a list of filters as a second argument. Vitest will run only tests that contain at least one of the passed-down strings in their file path. - -Additionally, you can use the third argument to pass in CLI arguments, which will override any test config options. - -Alternatively, you can pass in the complete Vite config as the fourth argument, which will take precedence over any other user-defined options. - -After running the tests, you can get the results from the `state.getFiles` API: - -```ts -const vitest = await startVitest('test') - -console.log(vitest.state.getFiles()) // [{ type: 'file', ... }] -``` - -Since Vitest 2.1, it is recommended to use the ["Reported Tasks" API](/advanced/reporters#reported-tasks) together with the `state.getFiles`. In the future, Vitest will return those objects directly: - -```ts -const vitest = await startVitest('test') - -const [fileTask] = vitest.state.getFiles() -const testFile = vitest.state.getReportedEntity(fileTask) -``` - -## createVitest - -You can create Vitest instance yourself using `createVitest` function. It returns the same `Vitest` instance as `startVitest`, but it doesn't start tests and doesn't validate installed packages. - -```js -import { createVitest } from 'vitest/node' - -const vitest = await createVitest('test', { - watch: false, -}) -``` - -## parseCLI - -You can use this method to parse CLI arguments. It accepts a string (where arguments are split by a single space) or a strings array of CLI arguments in the same format that Vitest CLI uses. It returns a filter and `options` that you can later pass down to `createVitest` or `startVitest` methods. - -```ts -import { parseCLI } from 'vitest/node' - -parseCLI('vitest ./files.ts --coverage --browser=chrome') -``` - -## Vitest - -Vitest instance requires the current test mode. It can be either: - -- `test` when running runtime tests -- `benchmark` when running benchmarks - -### mode - -#### test - -Test mode will only call functions inside `test` or `it`, and throws an error when `bench` is encountered. This mode uses `include` and `exclude` options in the config to find test files. - -#### benchmark - -Benchmark mode calls `bench` functions and throws an error, when it encounters `test` or `it`. This mode uses `benchmark.include` and `benchmark.exclude` options in the config to find benchmark files. - -### start - -You can start running tests or benchmarks with `start` method. You can pass an array of strings to filter test files. - -### config - -The root (or global) config. If workspace feature is enabled, projects will reference this as `globalConfig`. - -::: warning -This is Vitest config, it doesn't extend _Vite_ config. It only has resolved values from the `test` property. -::: - -### vite - -This is a global [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). - -### state - -::: warning -Public state is an experimental state. Breaking changes might not follow SemVer, please pin Vitest's version when using it. -::: - -Global state stores information about the current tests. It uses the same API from `@vitest/runner` by default, but we recommend using the [Reported API](/advanced/reporters#reported-tasks) instead by calling `state.getReportedEntity()` on the `@vitest/runner` API: - -```ts -const task = vitest.state.idMap.get(taskId) // old API -const testCase = vitest.state.getReportedEntity(task) // new API -``` - -In the future, the old API won't be exposed anymore. - -### snapshot - -The global snapshot manager. Vitest keeps track of all snapshots using the `snapshot.add` method. - -You can get the latest summary of snapshots via the `vitest.snapshot.summay` property. - -### cache - -Cache manager that stores information about latest test results and test file stats. In Vitest itself this is only used by the default sequencer to sort tests. - -### ready - -Vitest needs to be resolved with the Vite server to be properly initialized. If the `Vitest` instance was created manually, you might need to check the `ready` status before accessing the `vite`, `state`, `cache`, `config`, and `snapshot` properties; otherwise, they will throw an error in the getter. - -In normal circumstances, you would never call this method because `createVitest` and `startVitest` return already resolved Vitest instance. - -### getRootTestProject - -This returns the root test project. The root project generally doesn't run any tests and is not included in `vitest.projects` unless the user explicitly includes the root config in their workspace. - -The primary goal of the root project is to setup the global config. In fact, `rootProject.config` references `rootProject.globalConfig` and `vitest.config` directly. - -### provide - -Vitest exposes `provide` method which is a shorthand for `vitest.getRootTestProject().provide`. With this method you can pass down values from the main thread to tests. All values are checked with `structuredClone` before they are stored, but the values themselves are not cloned. - -To recieve the values in the test, you need to import `inject` method from `vitest` entrypont: - -```ts -import { inject } from 'vitest' -const port = inject('wsPort') // 3000 -``` - -For better type safety, we encourage you to augment the type of `ProvidedContext`: - -```ts -import { createVitest } from 'vitest/node' - -const vitest = await createVitest('test', { - watch: false, -}) -vitest.provide('wsPort', 3000) - -declare module 'vitest' { - export interface ProvidedContext { - wsPort: number - } -} -``` - -::: warning -Technically, `provide` is a method of [`TestProject`](#testproject), so it is limited to the specific project. However, all projects inherit the values from the core project which makes `vitest.provide` universal way of passing down values to tests. -::: - -### getProvidedContext - -This returns the root context object. This is a shorthand for `vitest.getRootTestProject().getProvidedContext`. - -### getProjectByName - -This method returns the project by its name. Simillar to calling `vitest.projects.find`. - -::: warning -In case the project doesn't exist, this method will return the root project - make sure to check the names again if you need to make sure the project you are looking for is the one returned. -::: - -### globTestSpecifications - -This method constructs new [test specifications](#testspecification) by collecting every test in all projects with [`project.globTestFiles`](#globtestfiles). It accepts string filters to match the test files. - -::: warning -As of Vitest 2.2.0, it's possible to have multiple test specifications with the same module ID (file path) if `poolMatchGlob` has several pools or if `typecheck` is enabled. -::: - -```ts -const specifications = await vitest.globTestSpecifications(['my-filter']) -// [TestSpecification{ moduleId: '/tests/my-filter.test.ts', pool: 'forks' }] -console.log(specifications) -``` - -### mergeReports -### collect -### listFiles -### start -### init - -### getModuleSpecifications - -Returns a list of test specifications related to the module ID. The ID should already be resolved to an absolute file path. If ID doesn't match `include` or `includeSource` patterns, the returned array will be empty. - -::: warning -As of Vitest 2.2.0, this method uses a cache to check if the file is a test. To make sure that the cache is not empty, call `globTestSpecifications` at least once. -::: - -### runTestSpecifications -### rerunTestSpecifications -### collectTests -### cancelCurrentRun -### updateSnapshot -### invalidateFile -### close -### exit - -### shouldKeepServer -### onServerRestart -### onCancel -### onClose -### onTestsRerun -### onFilterWatchedSpecification - -## TestProject 2.2.0 {#testproject} - -- **Alias**: `WorkspaceProject` before 2.2.0 - -### name - -The name is a unique string assigned by the user or interpreted by Vitest. If user did not provide a name, Vitest tries to load a `package.json` in the root of the project and takes the `name` property from there. If there is no `package.json`, Vitest uses the name of the folder by default. Inline projects use numbers as the name (converted to string). - -::: code-group -```ts [node.js] -import { createVitest } from 'vitest/node' - -const vitest = await createVitest('test') -vitest.projects.map(p => p.name) === [ - '@pkg/server', - 'utils', - '2', - 'custom' -] -``` -```ts [vitest.workspace.js] -export default [ - './packages/server', // has package.json with "@pkg/server" - './utils', // doesn't have a package.json file - { - // doesn't customize the name - test: { - pool: 'threads', - }, - }, - { - // customized the name - test: { - name: 'custom', - }, - }, -] -``` -::: - -### vitest - -`vitest` references the global [`vitest`](#vitest) process. - -### serializedConfig - -This is the test config that all tests will receive. Vitest [serializes config](https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/config/serializeConfig.ts) manually by removing all functions and properties that are not possible to serialize. Since this value is available in both tests and node, it is exported from the main entry point. - -```ts -import type { SerializedConfig } from 'vitest' - -const config: SerializedConfig = vitest.projects[0].serializedConfig -``` - -### globalConfig - -The test config that `vitest` was initialized with. If this is the root project, `globalConfig` and `config` will reference the same object. This config is useful for values that cannot be set on the project level, like `coverage` or `reporters`. - -```ts -import type { ResolvedConfig } from 'vitest/node' - -vitest.config === vitest.projects[0].globalConfig -``` - -### config - -This is the project's resolved test config. - -### vite - -This is project's [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). All projects have their own Vite servers. - -### browser - -This value will be set only if tests are running in the browser. If `browser` is enabled, but tests didn't run yet, this will be `undefined`. If you need to check if the project supports browser tests, use `project.isBrowserSupported()` method. - -::: warning -The browser API is even more experimental and doesn't follow SemVer. The browser API will be standardized separately from the rest of the APIs. -::: - -### provide - -A way to provide custom values to tests in addition to [`config.provide`](/config/#provide) field. All values are validated with [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) before they are stored, but the values on `providedContext` themselves are not cloned. - -::: code-group -```ts [node.js] -import { createVitest } from 'vitest/node' - -const vitest = await createVitest('test') -const project = vitest.projects.find(p => p.name === 'custom') -project.provide('key', 'value') -await vitest.start() -``` -```ts [test.spec.js] -import { inject } from 'vitest' -const value = inject('key') -``` -::: - -The values can be provided dynamicaly. Provided value in tests will be updated on their next run. - -::: tip -This method is also available to [global setup files](/config/#globalsetup) for cases where you cannot use the public API: - -```js -export default function setup({ provide }) { - provide('wsPort', 3000) -} -``` -::: - -### getProvidedContext - -This returns the context object. Every project also inherits the global context set by `vitest.provide`. - -```ts -import { createVitest } from 'vitest/node' - -const vitest = await createVitest('test') -vitest.provide('global', true) -const project = vitest.projects.find(p => p.name === 'custom') -project.provide('key', 'value') - -// { global: true, key: 'value' } -const context = project.getProvidedContext() -``` - -::: tip -Project context values will always override global ones. -::: - -### createSpecification - -Create a test specification that can be used in `vitest.runFiles`. Specification scopes the test file to a specific `project` and `pool` (optionally). - -```ts -import { createVitest } from 'vitest/node' -import { resolve } from 'node:path/posix' - -const vitest = await createVitest('test') -const project = vitest.projects[0] -const specification = project.createSpecification( - resolve('./basic.test.ts'), - 'threads', // optional override -) -await vitest.runFiles([specification], true) -``` - -::: warning -`createSpecification` expects an absolute file path. It doesn't resolve the file or check that it exists on the file system. -::: - -### isRootProject - -Checks if the current project is the root project. You can also get the root project by calling [`vitest.getRootTestProject()`](#getroottestproject). - -### globTestFiles - -Globs all test files. This function returns an object with regular tests and typecheck tests: - -```ts -interface GlobReturn { - /** - * Test files that match the filters. - */ - testFiles: string[] - /** - * Typecheck test files that match the filters. This will be empty unless `typecheck.enabled` is `true`. - */ - typecheckTestFiles: string[] -} -``` - -::: tip -Vitest uses [fast-glob](https://www.npmjs.com/package/fast-glob) to find test files. `test.dir`, `test.root`, `root` or `process.cwd()` define the `cwd` option. - -This method looks at several config options: - -- `test.include`, `test.exclude` to find regular test files -- `test.includeSource`, `test.exclude` to find in-source tests -- `test.typecheck.include`, `test.typecheck.exclude` to find typecheck tests -::: - -### matchesTestGlob - -This method checks if the file is a regular test file. It uses the same config properties that `globTestFiles` uses for validation. - -This method also accepts a second parameter, which is the source code. This is used to validate if the file is an in-source test. If you are calling this method several times for several projects it is recommended to read the file once and pass it down directly. - -```ts -import { createVitest } from 'vitest/node' -import { resolve } from 'node:path/posix' - -const vitest = await createVitest('test') -const project = vitest.projects[0] - -project.matchesTestGlob(resolve('./basic.test.ts')) // true -project.matchesTestGlob(resolve('./basic.ts')) // false -project.matchesTestGlob(resolve('./basic.ts'), ` -if (import.meta.vitest) { - // ... -} -`) // true if `includeSource` is set -``` - -### onTestsRerun - -This is a shorthand for `project.vitest.onTestsRerun`. It accepts a callback that will be awaited when the tests have been scheduled to rerun (usually, due to a file change). - -```ts -project.onTestsRerun((specs) => { - console.log(specs) -}) -``` - -### close - -Closes the project and all associated resources. This can only be called once; the closing promise is cached until the server restarts. If the resources are needed again, create a new project. - -In detail, this method closes the Vite server, stops the typechecker service, closes the browser if it's running, deletes the temporary directory that holds the source code, and resets the provided context. - -## TestSpecification - - diff --git a/docs/advanced/api/index.md b/docs/advanced/api/index.md new file mode 100644 index 0000000000000..cffbd2175d86d --- /dev/null +++ b/docs/advanced/api/index.md @@ -0,0 +1,102 @@ +# Getting Started + +::: warning +This guide lists advanced APIs to run tests via a Node.js script. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. +::: + +## startVitest + +```ts +function startVitest( + mode: VitestRunMode, + cliFilters: string[] = [], + options: CliOptions = {}, + viteOverrides?: ViteUserConfig, + vitestOptions?: VitestOptions, +): Promise +``` + +You can start running Vitest tests using its Node API: + +```js +import { startVitest } from 'vitest/node' + +const vitest = await startVitest('test') + +await vitest.close() +``` + +`startVitest` function returns [`Vitest`](/advanced/api/vitest) instance if tests can be started. + +If watch mode is not enabled, Vitest will call `close` method automatically. + +If watch mode is enabled and the terminal supports TTY, Vitest will register console shortcuts. + +You can pass down a list of filters as a second argument. Vitest will run only tests that contain at least one of the passed-down strings in their file path. + +Additionally, you can use the third argument to pass in CLI arguments, which will override any test config options. Alternatively, you can pass in the complete Vite config as the fourth argument, which will take precedence over any other user-defined options. + +After running the tests, you can get the results from the [`state.getTestModules`](/advanced/reporters#reported-tasks) API: + +```ts +import type { TestModule } from 'vitest/node' + +const vitest = await startVitest('test') + +console.log(vitest.state.getTestModules()) // [TestModule] +``` + +::: tip +The ["Running Tests"](/advanced/guide/tests#startvitest) guide has a usage example. +::: + +## createVitest + +```ts +function createVitest( + mode: VitestRunMode, + options: UserConfig, + viteOverrides: ViteUserConfig = {}, + vitestOptions: VitestOptions = {}, +): Promise +``` + +You can create Vitest instance by using `createVitest` function. It returns the same [`Vitest`](/advanced/api/vitest) instance as `startVitest`, but it doesn't start tests and doesn't validate installed packages. + +```js +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test', { + watch: false, +}) +``` + +::: tip +The ["Running Tests"](/advanced/guide/tests#createvitest) guide has a usage example. +::: + +## parseCLI + +```ts +function parseCLI(argv: string | string[], config: CliParseOptions = {}): { + filter: string[] + options: CliOptions +} +``` + +You can use this method to parse CLI arguments. It accepts a string (where arguments are split by a single space) or a strings array of CLI arguments in the same format that Vitest CLI uses. It returns a filter and `options` that you can later pass down to `createVitest` or `startVitest` methods. + +```ts +import { parseCLI } from 'vitest/node' + +const result = parseCLI('vitest ./files.ts --coverage --browser=chrome') + +result.options +// { +// coverage: { enabled: true }, +// browser: { name: 'chrome', enabled: true } +// } + +result.filter +// ['./files.ts'] +``` diff --git a/docs/advanced/api/test-case.md b/docs/advanced/api/test-case.md new file mode 100644 index 0000000000000..d6f4004f6ea31 --- /dev/null +++ b/docs/advanced/api/test-case.md @@ -0,0 +1,136 @@ +# TestCase + +The `TestCase` class represents a single test. + +```ts +declare class TestCase { + readonly type = 'test' + /** + * The project associated with the test. + */ + readonly project: TestProject + /** + * Direct reference to the test module where the test is defined. + */ + readonly module: TestModule + /** + * Name of the test. + */ + readonly name: string + /** + * Full name of the test including all parent suites separated with `>`. + */ + readonly fullName: string + /** + * Unique identifier. + * This ID is deterministic and will be the same for the same test across multiple runs. + * The ID is based on the project name, module id and test position. + */ + readonly id: string + /** + * Location in the module where the test was defined. + * Locations are collected only if `includeTaskLocation` is enabled in the config. + */ + readonly location: { line: number; column: number } | undefined + /** + * Parent suite. If the test was called directly inside the module, the parent will be the module itself. + */ + readonly parent: TestSuite | TestModule + /** + * Options that test was initiated with. + */ + readonly options: TaskOptions + /** + * Checks if the test did not fail the suite. + * If the test is not finished yet or was skipped, it will return `true`. + */ + ok(): boolean + /** + * Checks if the test was skipped. + */ + skipped(): boolean + /** + * Custom metadata that was attached to the test during its execution. + */ + meta(): TaskMeta + /** + * Test results. Will be `undefined` if test is not finished yet or was just collected. + */ + result(): TestResult | undefined + /** + * Useful information about the test like duration, memory usage, etc. + */ + diagnostic(): TestDiagnostic | undefined +} + +export type TestResult = TestResultPassed | TestResultFailed | TestResultSkipped + +export interface TestResultPassed { + /** + * The test passed successfully. + */ + state: 'passed' + /** + * Errors that were thrown during the test execution. + * + * **Note**: If test was retried successfully, errors will still be reported. + */ + errors: TestError[] | undefined +} + +export interface TestResultFailed { + /** + * The test failed to execute. + */ + state: 'failed' + /** + * Errors that were thrown during the test execution. + */ + errors: TestError[] +} + +export interface TestResultSkipped { + /** + * The test was skipped with `only`, `skip` or `todo` flag. + * You can see which one was used in the `mode` option. + */ + state: 'skipped' + /** + * Skipped tests have no errors. + */ + errors: undefined +} + +export interface TestDiagnostic { + /** + * If the duration of the test is above `slowTestThreshold`. + */ + slow: boolean + /** + * The amount of memory used by the test in bytes. + * This value is only available if the test was executed with `logHeapUsage` flag. + */ + heap: number | undefined + /** + * The time it takes to execute the test in ms. + */ + duration: number + /** + * The time in ms when the test started. + */ + startTime: number + /** + * The amount of times the test was retried. + */ + retryCount: number + /** + * The amount of times the test was repeated as configured by `repeats` option. + * This value can be lower if the test failed during the repeat and no `retry` is configured. + */ + repeatCount: number + /** + * If test passed on a second retry. + */ + flaky: boolean +} +``` diff --git a/docs/advanced/api/test-collection.md b/docs/advanced/api/test-collection.md new file mode 100644 index 0000000000000..d01e84962c615 --- /dev/null +++ b/docs/advanced/api/test-collection.md @@ -0,0 +1,50 @@ +# TestCollection + +`TestCollection` represents a collection of suites and tests. It also provides useful methods to iterate over itself. + +```ts +declare class TestCollection { + /** + * Returns the test or suite at a specific index in the array. + */ + at(index: number): TestCase | TestSuite | undefined + /** + * The number of tests and suites in the collection. + */ + size: number + /** + * Returns the collection in array form for easier manipulation. + */ + array(): (TestCase | TestSuite)[] + /** + * Filters all suites that are part of this collection and its children. + */ + allSuites(): IterableIterator + /** + * Filters all tests that are part of this collection and its children. + */ + allTests(state?: TestResult['state'] | 'running'): IterableIterator + /** + * Filters only the tests that are part of this collection. + */ + tests(state?: TestResult['state'] | 'running'): IterableIterator + /** + * Filters only the suites that are part of this collection. + */ + suites(): IterableIterator; + [Symbol.iterator](): IterableIterator +} +``` + +For example, you can iterate over all tests inside a module by calling `testModule.children.allTests()`: + +```ts +function onFileCollected(testModule: TestModule): void { + console.log('collecting tests in', testModule.moduleId) + + // iterate over all tests and suites in the module + for (const task of testModule.children.allTests()) { + console.log('collected', task.type, task.fullName) + } +} +``` diff --git a/docs/advanced/api/test-module.md b/docs/advanced/api/test-module.md new file mode 100644 index 0000000000000..76ad8fb7a3ae2 --- /dev/null +++ b/docs/advanced/api/test-module.md @@ -0,0 +1,46 @@ +# TestModule + +```ts +declare class TestModule extends TestSuite { + readonly type = 'module' + /** + * Collection of suites and tests that are part of this module. + */ + readonly children: TestCollection + /** + * This is usually an absolute Unix file path. + * It can be a virtual id if the file is not on the disk. + * This value corresponds to Vite's `ModuleGraph` id. + */ + readonly moduleId: string + /** + * Useful information about the module like duration, memory usage, etc. + * If the module was not executed yet, all diagnostic values will return `0`. + */ + diagnostic(): ModuleDiagnostic +} + +export interface ModuleDiagnostic { + /** + * The time it takes to import and initiate an environment. + */ + environmentSetupDuration: number + /** + * The time it takes Vitest to setup test harness (runner, mocks, etc.). + */ + prepareDuration: number + /** + * The time it takes to import the test module. + * This includes importing everything in the module and executing suite callbacks. + */ + collectDuration: number + /** + * The time it takes to import the setup module. + */ + setupDuration: number + /** + * Accumulated duration of all tests and hooks in the module. + */ + duration: number +} +``` diff --git a/docs/advanced/api/test-project.md b/docs/advanced/api/test-project.md new file mode 100644 index 0000000000000..eaedd5d9afc29 --- /dev/null +++ b/docs/advanced/api/test-project.md @@ -0,0 +1,276 @@ +--- +title: TestProject +--- + +# TestProject 2.2.0 {#testproject} + +- **Alias**: `WorkspaceProject` before 2.2.0 + +::: warning +This guide describes the advanced Node.js API. If you just want to create a workspace, follow the ["Workspace"](/guide/workspace) guide. +::: + +## name + +The name is a unique string assigned by the user or interpreted by Vitest. If user did not provide a name, Vitest tries to load a `package.json` in the root of the project and takes the `name` property from there. If there is no `package.json`, Vitest uses the name of the folder by default. Inline projects use numbers as the name (converted to string). + +::: code-group +```ts [node.js] +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +vitest.projects.map(p => p.name) === [ + '@pkg/server', + 'utils', + '2', + 'custom' +] +``` +```ts [vitest.workspace.js] +export default [ + './packages/server', // has package.json with "@pkg/server" + './utils', // doesn't have a package.json file + { + // doesn't customize the name + test: { + pool: 'threads', + }, + }, + { + // customized the name + test: { + name: 'custom', + }, + }, +] +``` +::: + +::: info +If the [root project](/advanced/api/vitest#getroottestproject) is not part of a user workspace, its `name` will not be resolved. +::: + +## vitest + +`vitest` references the global [`Vitest`](/advanced/api/vitest) process. + +## serializedConfig + +This is the config that test processes receive. Vitest [serializes config](https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/config/serializeConfig.ts) manually by removing all functions and properties that are not possible to serialize. Since this value is available in both tests and node, its type is exported from the main entry point. + +```ts +import type { SerializedConfig } from 'vitest' + +const config: SerializedConfig = vitest.projects[0].serializedConfig +``` + +::: warning +The `serializedConfig` property is a getter. Every time it's accessed Vitest serializes the config again in case it was changed. This also means that it always returns a different reference: + +```ts +project.serializedConfig === project.serializedConfig // false +``` +::: + +## globalConfig + +The test config that [`Vitest`](/advanced/api/vitest) was initialized with. If this is the [root project](/advanced/api/vitest#getroottestproject), `globalConfig` and `config` will reference the same object. This config is useful for values that cannot be set on the project level, like `coverage` or `reporters`. + +```ts +import type { ResolvedConfig } from 'vitest/node' + +vitest.config === vitest.projects[0].globalConfig +``` + +## config + +This is the project's resolved test config. + +## vite + +This is project's [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). All projects have their own Vite servers. + +## browser + +This value will be set only if tests are running in the browser. If `browser` is enabled, but tests didn't run yet, this will be `undefined`. If you need to check if the project supports browser tests, use `project.isBrowserSupported()` method. + +::: warning +The browser API is even more experimental and doesn't follow SemVer. The browser API will be standardized separately from the rest of the APIs. +::: + +## provide + +```ts +function provide( + key: T, + value: ProvidedContext[T], +): void +``` + +A way to provide custom values to tests in addition to [`config.provide`](/config/#provide) field. All values are validated with [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) before they are stored, but the values on `providedContext` themselves are not cloned. + +::: code-group +```ts [node.js] +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +const project = vitest.projects.find(p => p.name === 'custom') +project.provide('key', 'value') +await vitest.start() +``` +```ts [test.spec.js] +import { inject } from 'vitest' +const value = inject('key') +``` +::: + +The values can be provided dynamicaly. Provided value in tests will be updated on their next run. + +::: tip +This method is also available to [global setup files](/config/#globalsetup) for cases where you cannot use the public API: + +```js +export default function setup({ provide }) { + provide('wsPort', 3000) +} +``` +::: + +## getProvidedContext + +```ts +function getProvidedContext(): ProvidedContext +``` + +This returns the context object. Every project also inherits the global context set by `vitest.provide`. + +```ts +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +vitest.provide('global', true) +const project = vitest.projects.find(p => p.name === 'custom') +project.provide('key', 'value') + +// { global: true, key: 'value' } +const context = project.getProvidedContext() +``` + +::: tip +Project context values will always override root project's context. +::: + +## createSpecification + +```ts +function createSpecification( + moduleId: string, + locations?: number[], + pool?: string, +): TestSpecification +``` + +Create a [test specification](/advanced/api/test-specification) that can be used in [`vitest.runTestSpecifications`](/advanced/api/vitest#runtestspecifications). Specification scopes the test file to a specific `project`, `locations` (optional) and `pool` (optional). + +```ts +import { createVitest } from 'vitest/node' +import { resolve } from 'node:path/posix' + +const vitest = await createVitest('test') +const project = vitest.projects[0] +const specification = project.createSpecification( + resolve('./basic.test.ts'), + [20, 40], // optional test lines + 'threads', // optional override +) +await vitest.runFiles([specification], true) +``` + +::: warning +`createSpecification` expects an absolute file path. It doesn't resolve the file or check that it exists on the file system. +::: + +## isRootProject + +```ts +function isRootProject(): boolean +``` + +Checks if the current project is the root project. You can also get the root project by calling [`vitest.getRootTestProject()`](#getroottestproject). + +## globTestFiles + +```ts +function globTestFiles(filters?: string[]): { + /** + * Test files that match the filters. + */ + testFiles: string[] + /** + * Typecheck test files that match the filters. This will be empty unless `typecheck.enabled` is `true`. + */ + typecheckTestFiles: string[] +} +``` + +Globs all test files. This function returns an object with regular tests and typecheck tests. + +::: tip +Vitest uses [fast-glob](https://www.npmjs.com/package/fast-glob) to find test files. `test.dir`, `test.root`, `root` or `process.cwd()` define the `cwd` option. + +This method looks at several config options: + +- `test.include`, `test.exclude` to find regular test files +- `test.includeSource`, `test.exclude` to find in-source tests +- `test.typecheck.include`, `test.typecheck.exclude` to find typecheck tests +::: + +## matchesTestGlob + +```ts +function matchesTestGlob(moduleId: string, source?: string): boolean +``` + +This method checks if the file is a regular test file. It uses the same config properties that `globTestFiles` uses for validation. + +This method also accepts a second parameter, which is the source code. This is used to validate if the file is an in-source test. If you are calling this method several times for several projects it is recommended to read the file once and pass it down directly. If the file is not a test file, but matches the `includeSource` glob, Vitest will synchronously read the file unless the `source` is provided. + +```ts +import { createVitest } from 'vitest/node' +import { resolve } from 'node:path/posix' + +const vitest = await createVitest('test') +const project = vitest.projects[0] + +project.matchesTestGlob(resolve('./basic.test.ts')) // true +project.matchesTestGlob(resolve('./basic.ts')) // false +project.matchesTestGlob(resolve('./basic.ts'), ` +if (import.meta.vitest) { + // ... +} +`) // true if `includeSource` is set +``` + +## onTestsRerun + +```ts +function onTestsRerun(cb: OnTestsRerunHandler): void +``` + +This is a shorthand for `project.vitest.onTestsRerun`. It accepts a callback that will be awaited when the tests have been scheduled to rerun (usually, due to a file change). + +```ts +project.onTestsRerun((specs) => { + console.log(specs) +}) +``` + +## close + +```ts +function close(): Promise +``` + +Closes the project and all associated resources. This can only be called once; the closing promise is cached until the server restarts. If the resources are needed again, create a new project. + +In detail, this method closes the Vite server, stops the typechecker service, closes the browser if it's running, deletes the temporary directory that holds the source code, and resets the provided context. diff --git a/docs/advanced/api/test-specification.md b/docs/advanced/api/test-specification.md new file mode 100644 index 0000000000000..112b3c678c5cc --- /dev/null +++ b/docs/advanced/api/test-specification.md @@ -0,0 +1,3 @@ +# TestSpecification + + diff --git a/docs/advanced/api/test-suite.md b/docs/advanced/api/test-suite.md new file mode 100644 index 0000000000000..f47beff9d4924 --- /dev/null +++ b/docs/advanced/api/test-suite.md @@ -0,0 +1,46 @@ +# TestSuite + +```ts +declare class TestSuite { + readonly type = 'suite' + /** + * The project associated with the test. + */ + readonly project: TestProject + /** + * Direct reference to the test module where the suite is defined. + */ + readonly module: TestModule + /** + * Name of the suite. + */ + readonly name: string + /** + * Full name of the suite including all parent suites separated with `>`. + */ + readonly fullName: string + /** + * Unique identifier. + * This ID is deterministic and will be the same for the same test across multiple runs. + * The ID is based on the project name, module id and test position. + */ + readonly id: string + /** + * Location in the module where the suite was defined. + * Locations are collected only if `includeTaskLocation` is enabled in the config. + */ + readonly location: { line: number; column: number } | undefined + /** + * Collection of suites and tests that are part of this suite. + */ + readonly children: TaskCollection + /** + * Options that the suite was initiated with. + */ + readonly options: TaskOptions + /** + * Errors that happened outside of the test run during collection, like syntax errors. + */ + public errors(): TestError[] +} +``` diff --git a/docs/advanced/api/vitest.md b/docs/advanced/api/vitest.md new file mode 100644 index 0000000000000..ae81cd5dd2df0 --- /dev/null +++ b/docs/advanced/api/vitest.md @@ -0,0 +1,165 @@ +--- +outline: deep +--- + +# Vitest + +Vitest instance requires the current test mode. It can be either: + +- `test` when running runtime tests +- `benchmark` when running benchmarks + +## mode + +### test + +Test mode will only call functions inside `test` or `it`, and throws an error when `bench` is encountered. This mode uses `include` and `exclude` options in the config to find test files. + +### benchmark + +Benchmark mode calls `bench` functions and throws an error, when it encounters `test` or `it`. This mode uses `benchmark.include` and `benchmark.exclude` options in the config to find benchmark files. + +## start + +```ts +function start(filters: string[]): Promise +``` + +You can start running tests or benchmarks with `start` method. You can pass an array of strings to filter test files. + +## config + +The root (or global) config. If workspace feature is enabled, projects will reference this as `globalConfig`. + +::: warning +This is Vitest config, it doesn't extend _Vite_ config. It only has resolved values from the `test` property. +::: + +## vite + +This is a global [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). + +## state + +::: warning +Public `state` is an experimental API. Breaking changes might not follow SemVer, please pin Vitest's version when using it. +::: + +Global state stores information about the current tests. It uses the same API from `@vitest/runner` by default, but we recommend using the [Reported API](/advanced/reporters#reported-tasks) instead by calling `state.getReportedEntity()` on the `@vitest/runner` API: + +```ts +const task = vitest.state.idMap.get(taskId) // old API +const testCase = vitest.state.getReportedEntity(task) // new API +``` + +In the future, the old API won't be exposed anymore. + +## snapshot + +The global snapshot manager. Vitest keeps track of all snapshots using the `snapshot.add` method. + +You can get the latest summary of snapshots via the `vitest.snapshot.summay` property. + +## cache + +Cache manager that stores information about latest test results and test file stats. In Vitest itself this is only used by the default sequencer to sort tests. + +## ready + +Vitest needs to be resolved with the Vite server to be properly initialized. If the `Vitest` instance was created manually, you might need to check the `ready` status before accessing the `vite`, `state`, `cache`, `config`, and `snapshot` properties; otherwise, they will throw an error in the getter. + +In normal circumstances, you would never call this method because `createVitest` and `startVitest` return already resolved Vitest instance. + +## getRootTestProject + +This returns the root test project. The root project generally doesn't run any tests and is not included in `vitest.projects` unless the user explicitly includes the root config in their workspace. + +The primary goal of the root project is to setup the global config. In fact, `rootProject.config` references `rootProject.globalConfig` and `vitest.config` directly. + +## provide + +Vitest exposes `provide` method which is a shorthand for `vitest.getRootTestProject().provide`. With this method you can pass down values from the main thread to tests. All values are checked with `structuredClone` before they are stored, but the values themselves are not cloned. + +To recieve the values in the test, you need to import `inject` method from `vitest` entrypont: + +```ts +import { inject } from 'vitest' +const port = inject('wsPort') // 3000 +``` + +For better type safety, we encourage you to augment the type of `ProvidedContext`: + +```ts +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test', { + watch: false, +}) +vitest.provide('wsPort', 3000) + +declare module 'vitest' { + export interface ProvidedContext { + wsPort: number + } +} +``` + +::: warning +Technically, `provide` is a method of [`TestProject`](#testproject), so it is limited to the specific project. However, all projects inherit the values from the core project which makes `vitest.provide` universal way of passing down values to tests. +::: + +## getProvidedContext + +This returns the root context object. This is a shorthand for `vitest.getRootTestProject().getProvidedContext`. + +## getProjectByName + +This method returns the project by its name. Simillar to calling `vitest.projects.find`. + +::: warning +In case the project doesn't exist, this method will return the root project - make sure to check the names again if you need to make sure the project you are looking for is the one returned. +::: + +## globTestSpecifications + +This method constructs new [test specifications](#testspecification) by collecting every test in all projects with [`project.globTestFiles`](#globtestfiles). It accepts string filters to match the test files. + +::: warning +As of Vitest 2.2.0, it's possible to have multiple test specifications with the same module ID (file path) if `poolMatchGlob` has several pools or if `typecheck` is enabled. This possibility will be removed in Vitest 3. +::: + +```ts +const specifications = await vitest.globTestSpecifications(['my-filter']) +// [TestSpecification{ moduleId: '/tests/my-filter.test.ts' }] +console.log(specifications) +``` + +## mergeReports +## collect +## listFiles +## start +## init + +## getModuleSpecifications + +Returns a list of test specifications related to the module ID. The ID should already be resolved to an absolute file path. If ID doesn't match `include` or `includeSource` patterns, the returned array will be empty. + +::: warning +As of Vitest 2.2.0, this method uses a cache to check if the file is a test. To make sure that the cache is not empty, call `globTestSpecifications` at least once. +::: + +## runTestSpecifications +## rerunTestSpecifications +## collectTests +## cancelCurrentRun +## updateSnapshot +## invalidateFile +## close +## exit + +## shouldKeepServer +## onServerRestart +## onCancel +## onClose +## onTestsRerun +## onFilterWatchedSpecification diff --git a/docs/advanced/pool.md b/docs/advanced/pool.md index 58932f1d8a9b7..7ca6914d53900 100644 --- a/docs/advanced/pool.md +++ b/docs/advanced/pool.md @@ -1,7 +1,7 @@ # Custom Pool ::: warning -This is advanced API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. +This is an advanced API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. ::: Vitest runs tests in pools. By default, there are several pools: diff --git a/docs/advanced/reporters.md b/docs/advanced/reporters.md index ba0d19193a4aa..38027752033fd 100644 --- a/docs/advanced/reporters.md +++ b/docs/advanced/reporters.md @@ -1,5 +1,9 @@ # Extending Reporters +::: warning +This is an advanced API. If you just want to configure built-in reporters, read the ["Reporters"](/guide/reporters) guide. +::: + You can import reporters from `vitest/reporters` and extend them to create your custom reporters. ## Extending Built-in Reporters @@ -56,8 +60,7 @@ export default defineConfig({ ## Reported Tasks -::: warning -This is an experimental API. Breaking changes might not follow SemVer. Please pin Vitest's version when using it. +Instead of using the tasks that reporters receive, it is recommended to use the Reported Tasks API instead. You can get access to this API by calling `vitest.state.getReportedEntity(runnerTask)`: @@ -85,293 +88,6 @@ class MyReporter implements Reporter { } } ``` -::: - -### TestCase - -`TestCase` represents a single test. - -```ts -declare class TestCase { - readonly type = 'test' - /** - * The project associated with the test. - */ - readonly project: TestProject - /** - * Direct reference to the test module where the test is defined. - */ - readonly module: TestModule - /** - * Name of the test. - */ - readonly name: string - /** - * Full name of the test including all parent suites separated with `>`. - */ - readonly fullName: string - /** - * Unique identifier. - * This ID is deterministic and will be the same for the same test across multiple runs. - * The ID is based on the project name, module id and test position. - */ - readonly id: string - /** - * Location in the module where the test was defined. - * Locations are collected only if `includeTaskLocation` is enabled in the config. - */ - readonly location: { line: number; column: number } | undefined - /** - * Parent suite. If the test was called directly inside the module, the parent will be the module itself. - */ - readonly parent: TestSuite | TestModule - /** - * Options that test was initiated with. - */ - readonly options: TaskOptions - /** - * Checks if the test did not fail the suite. - * If the test is not finished yet or was skipped, it will return `true`. - */ - ok(): boolean - /** - * Checks if the test was skipped. - */ - skipped(): boolean - /** - * Custom metadata that was attached to the test during its execution. - */ - meta(): TaskMeta - /** - * Test results. Will be `undefined` if test is not finished yet or was just collected. - */ - result(): TestResult | undefined - /** - * Useful information about the test like duration, memory usage, etc. - */ - diagnostic(): TestDiagnostic | undefined -} - -export type TestResult = TestResultPassed | TestResultFailed | TestResultSkipped - -export interface TestResultPassed { - /** - * The test passed successfully. - */ - state: 'passed' - /** - * Errors that were thrown during the test execution. - * - * **Note**: If test was retried successfully, errors will still be reported. - */ - errors: TestError[] | undefined -} - -export interface TestResultFailed { - /** - * The test failed to execute. - */ - state: 'failed' - /** - * Errors that were thrown during the test execution. - */ - errors: TestError[] -} - -export interface TestResultSkipped { - /** - * The test was skipped with `only`, `skip` or `todo` flag. - * You can see which one was used in the `mode` option. - */ - state: 'skipped' - /** - * Skipped tests have no errors. - */ - errors: undefined -} - -export interface TestDiagnostic { - /** - * If the duration of the test is above `slowTestThreshold`. - */ - slow: boolean - /** - * The amount of memory used by the test in bytes. - * This value is only available if the test was executed with `logHeapUsage` flag. - */ - heap: number | undefined - /** - * The time it takes to execute the test in ms. - */ - duration: number - /** - * The time in ms when the test started. - */ - startTime: number - /** - * The amount of times the test was retried. - */ - retryCount: number - /** - * The amount of times the test was repeated as configured by `repeats` option. - * This value can be lower if the test failed during the repeat and no `retry` is configured. - */ - repeatCount: number - /** - * If test passed on a second retry. - */ - flaky: boolean -} -``` - -### TestSuite - -`TestSuite` represents a single suite that contains tests and other suites. - -```ts -declare class TestSuite { - readonly type = 'suite' - /** - * The project associated with the test. - */ - readonly project: TestProject - /** - * Direct reference to the test module where the suite is defined. - */ - readonly module: TestModule - /** - * Name of the suite. - */ - readonly name: string - /** - * Full name of the suite including all parent suites separated with `>`. - */ - readonly fullName: string - /** - * Unique identifier. - * This ID is deterministic and will be the same for the same test across multiple runs. - * The ID is based on the project name, module id and test position. - */ - readonly id: string - /** - * Location in the module where the suite was defined. - * Locations are collected only if `includeTaskLocation` is enabled in the config. - */ - readonly location: { line: number; column: number } | undefined - /** - * Collection of suites and tests that are part of this suite. - */ - readonly children: TaskCollection - /** - * Options that the suite was initiated with. - */ - readonly options: TaskOptions - /** - * Errors that happened outside of the test run during collection, like syntax errors. - */ - public errors(): TestError[] -} -``` - -### TestModule - -`TestModule` represents a single file that contains suites and tests. - -```ts -declare class TestModule extends TestSuite { - readonly type = 'module' - /** - * Collection of suites and tests that are part of this module. - */ - readonly children: TestCollection - /** - * This is usually an absolute Unix file path. - * It can be a virtual id if the file is not on the disk. - * This value corresponds to Vite's `ModuleGraph` id. - */ - readonly moduleId: string - /** - * Useful information about the module like duration, memory usage, etc. - * If the module was not executed yet, all diagnostic values will return `0`. - */ - diagnostic(): ModuleDiagnostic -} - -export interface ModuleDiagnostic { - /** - * The time it takes to import and initiate an environment. - */ - environmentSetupDuration: number - /** - * The time it takes Vitest to setup test harness (runner, mocks, etc.). - */ - prepareDuration: number - /** - * The time it takes to import the test module. - * This includes importing everything in the module and executing suite callbacks. - */ - collectDuration: number - /** - * The time it takes to import the setup module. - */ - setupDuration: number - /** - * Accumulated duration of all tests and hooks in the module. - */ - duration: number -} -``` - -### TestCollection - -`TestCollection` represents a collection of suites and tests. It also provides useful methods to iterate over itself. - -```ts -declare class TestCollection { - /** - * Returns the test or suite at a specific index in the array. - */ - at(index: number): TestCase | TestSuite | undefined - /** - * The number of tests and suites in the collection. - */ - size: number - /** - * Returns the collection in array form for easier manipulation. - */ - array(): (TestCase | TestSuite)[] - /** - * Filters all suites that are part of this collection and its children. - */ - allSuites(): IterableIterator - /** - * Filters all tests that are part of this collection and its children. - */ - allTests(state?: TestResult['state'] | 'running'): IterableIterator - /** - * Filters only the tests that are part of this collection. - */ - tests(state?: TestResult['state'] | 'running'): IterableIterator - /** - * Filters only the suites that are part of this collection. - */ - suites(): IterableIterator; - [Symbol.iterator](): IterableIterator -} -``` - -For example, you can iterate over all tests inside a module by calling `testModule.children.allTests()`: - -```ts -function onFileCollected(testModule: TestModule): void { - console.log('collecting tests in', testModule.moduleId) - - // iterate over all tests and suites in the module - for (const task of testModule.children.allTests()) { - console.log('collected', task.type, task.fullName) - } -} -``` ## Exported Reporters diff --git a/docs/advanced/runner.md b/docs/advanced/runner.md index ab5fb77e510bd..fa58f3f983cba 100644 --- a/docs/advanced/runner.md +++ b/docs/advanced/runner.md @@ -1,4 +1,4 @@ -# Test Runner +# Runner API ::: warning This is advanced API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. diff --git a/eslint.config.js b/eslint.config.js index fca823e74f115..e2adb7ed01fc1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -107,6 +107,7 @@ export default antfu( 'import/first': 'off', 'unused-imports/no-unused-imports': 'off', 'ts/method-signature-style': 'off', + 'no-self-compare': 'off', }, }, { diff --git a/packages/vitest/src/node/cli/cac.ts b/packages/vitest/src/node/cli/cac.ts index d1c8e78b103dc..925bf21ba0ade 100644 --- a/packages/vitest/src/node/cli/cac.ts +++ b/packages/vitest/src/node/cli/cac.ts @@ -58,7 +58,7 @@ function addCommand(cli: CAC | Command, name: string, option: CLIOption) { } } -interface CLIOptions { +export interface CliParseOptions { allowUnknownOptions?: boolean } @@ -70,7 +70,7 @@ function addCliOptions(cli: CAC | Command, options: CLIOptionsConfig) { } } -export function createCLI(options: CLIOptions = {}) { +export function createCLI(options: CliParseOptions = {}) { const cli = cac('vitest') cli.version(version) @@ -196,7 +196,7 @@ export function createCLI(options: CLIOptions = {}) { return cli } -export function parseCLI(argv: string | string[], config: CLIOptions = {}): { +export function parseCLI(argv: string | string[], config: CliParseOptions = {}): { filter: string[] options: CliOptions } { @@ -307,7 +307,7 @@ async function collect(mode: VitestRunMode, cliFilters: string[], options: CliOp run: true, }) if (!options.filesOnly) { - const { tests, errors } = await ctx.collect(cliFilters.map(normalize)) + const { testModules: tests, unhandledErrors: errors } = await ctx.collect(cliFilters.map(normalize)) if (errors.length) { console.error('\nThere were unhandled errors during test collection') diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index b3635d65a80a1..d72319f07ca00 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -490,8 +490,8 @@ export class Vitest { await this.coverageProvider?.mergeReports?.(coverages) return { - tests: this.state.getTestModules(), - errors: this.state.getUnhandledErrors(), + testModules: this.state.getTestModules(), + unhandledErrors: this.state.getUnhandledErrors(), } } @@ -502,7 +502,7 @@ export class Vitest { // if run with --changed, don't exit if no tests are found if (!files.length) { - return { tests: [], errors: [] } + return { testModules: [], unhandledErrors: [] } } return this.collectTests(files) @@ -553,8 +553,8 @@ export class Vitest { } let testModules: TestRunResult = { - tests: [], - errors: [], + testModules: [], + unhandledErrors: [], } if (files.length) { @@ -688,8 +688,8 @@ export class Vitest { await this.cache.results.writeToCache() return { - tests: this.state.getTestModules(), - errors: this.state.getUnhandledErrors(), + testModules: this.state.getTestModules(), + unhandledErrors: this.state.getUnhandledErrors(), } } finally { @@ -757,8 +757,8 @@ export class Vitest { } return { - tests: this.state.getTestModules(), - errors: this.state.getUnhandledErrors(), + testModules: this.state.getTestModules(), + unhandledErrors: this.state.getUnhandledErrors(), } })() .finally(() => { diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts index b850a02476e9c..e5bae926bdb20 100644 --- a/packages/vitest/src/node/project.ts +++ b/packages/vitest/src/node/project.ts @@ -133,8 +133,8 @@ export class TestProject { */ public createSpecification( moduleId: string, - pool?: string, locations?: number[] | undefined, + pool?: string, ): TestSpecification { return new TestSpecification( this, @@ -417,8 +417,8 @@ export class TestProject { /** * Test if a file matches the test globs. This does the actual glob matching unlike `isTestFile`. */ - public matchesTestGlob(filepath: string, source?: string): boolean { - const relativeId = relative(this.config.dir || this.config.root, filepath) + public matchesTestGlob(moduleId: string, source?: string): boolean { + const relativeId = relative(this.config.dir || this.config.root, moduleId) if (mm.isMatch(relativeId, this.config.exclude)) { return false } @@ -429,7 +429,7 @@ export class TestProject { this.config.includeSource?.length && mm.isMatch(relativeId, this.config.includeSource) ) { - const code = source || readFileSync(filepath, 'utf-8') + const code = source || readFileSync(moduleId, 'utf-8') return this.isInSourceTestCode(code) } return false diff --git a/packages/vitest/src/node/specifications.ts b/packages/vitest/src/node/specifications.ts index 4a5db0168adae..a0e0ce6d50a2c 100644 --- a/packages/vitest/src/node/specifications.ts +++ b/packages/vitest/src/node/specifications.ts @@ -24,7 +24,7 @@ export class VitestSpecifications { specs.push(project.createSpecification(moduleId)) } if (project.isTypecheckFile(moduleId)) { - specs.push(project.createSpecification(moduleId, 'typescript')) + specs.push(project.createSpecification(moduleId, [], 'typescript')) } } specs.forEach(spec => this.ensureSpecificationCached(spec)) @@ -66,7 +66,7 @@ export class VitestSpecifications { const loc = testLocations[file] testLocHasMatch[file] = true - const spec = project.createSpecification(file, undefined, loc) + const spec = project.createSpecification(file, loc) this.ensureSpecificationCached(spec) files.push(spec) }) @@ -74,7 +74,7 @@ export class VitestSpecifications { const loc = testLocations[file] testLocHasMatch[file] = true - const spec = project.createSpecification(file, 'typescript', loc) + const spec = project.createSpecification(file, loc, 'typescript') this.ensureSpecificationCached(spec) files.push(spec) }) diff --git a/packages/vitest/src/node/types/tests.ts b/packages/vitest/src/node/types/tests.ts index 63b76515f3a7c..9b05e1f97ee01 100644 --- a/packages/vitest/src/node/types/tests.ts +++ b/packages/vitest/src/node/types/tests.ts @@ -1,6 +1,6 @@ import type { TestModule } from '../reporters' export interface TestRunResult { - tests: TestModule[] - errors: unknown[] + testModules: TestModule[] + unhandledErrors: unknown[] } diff --git a/packages/vitest/src/public/node.ts b/packages/vitest/src/public/node.ts index cf8485ced728d..a5d703ea5fb88 100644 --- a/packages/vitest/src/public/node.ts +++ b/packages/vitest/src/public/node.ts @@ -3,6 +3,7 @@ import { createServer as _createServer } from 'vite' import { TestModule as _TestFile } from '../node/reporters/reported-tasks' export { parseCLI } from '../node/cli/cac' +export type { CliParseOptions } from '../node/cli/cac' export { startVitest } from '../node/cli/cli-api' export { resolveApiServerConfig, resolveConfig } from '../node/config/resolveConfig' export type { Vitest } from '../node/core' @@ -91,7 +92,6 @@ export type { VitestEnvironment, VitestRunMode, } from '../node/types/config' - export type { BaseCoverageOptions, CoverageIstanbulOptions, @@ -104,6 +104,8 @@ export type { ReportContext, ResolvedCoverageOptions, } from '../node/types/coverage' + +export type { TestRunResult } from '../node/types/tests' /** * @deprecated Use `TestModule` instead */