Skip to content

Commit

Permalink
refactor: merge TestProject with WorkspaceProject (#6906)
Browse files Browse the repository at this point in the history
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
  • Loading branch information
sheremet-va and AriPerkkio authored Nov 18, 2024
1 parent 0b08bc1 commit 171041a
Show file tree
Hide file tree
Showing 53 changed files with 875 additions and 528 deletions.
51 changes: 33 additions & 18 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,24 +216,39 @@ export default ({ mode }: { mode: string }) => {
{
items: [
{
text: 'Vitest Node API',
link: '/advanced/api',
},
{
text: 'Runner API',
link: '/advanced/runner',
},
{
text: 'Task Metadata',
link: '/advanced/metadata',
},
{
text: 'Extending Reporters',
link: '/advanced/reporters',
},
{
text: 'Custom Pool',
link: '/advanced/pool',
text: 'API',
items: [

{
text: 'Vitest Node API',
link: '/advanced/api',
},
{
text: 'Runner API',
link: '/advanced/runner',
},
{
text: 'Task Metadata',
link: '/advanced/metadata',
},
],
},
{
text: 'Guides',
items: [
{
text: 'Running Tests',
link: '/advanced/guide/tests',
},
{
text: 'Extending Reporters',
link: '/advanced/reporters',
},
{
text: 'Custom Pool',
link: '/advanced/pool',
},
],
},
],
},
Expand Down
215 changes: 212 additions & 3 deletions docs/advanced/api.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
outline: [2, 3]
---

# Node API

::: warning
Expand Down Expand Up @@ -96,7 +100,7 @@ You can start running tests or benchmarks with `start` method. You can pass an a
### `provide`
Vitest exposes `provide` method which is a shorthand for `vitest.getCoreWorkspaceProject().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.
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:
Expand All @@ -123,15 +127,220 @@ declare module 'vitest' {
```
::: warning
Technically, `provide` is a method of `WorkspaceProject`, 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.
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.
:::
::: tip
This method is also available to [global setup files](/config/#globalsetup) for cases where you don't want to use the public API:
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)
}
```
:::
## TestProject <Version>2.2.0</Version>
- **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`. 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.
### 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()`.
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.
### 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
```
### 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.
69 changes: 69 additions & 0 deletions docs/advanced/guide/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Running Tests

::: warning
This guide explains how to use the advanced API 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.

Breaking changes might not follow SemVer, please pin Vitest's version when using the experimental API.
:::

Vitest exposes two methods to initiate Vitest:

- `startVitest` initiates Vitest, validates the packages are installed and runs tests immidiatly
- `createVitest` only initiates Vitest and doesn't run any tests

## `startVitest`

```ts
import { startVitest } from 'vitest/node'

const vitest = await startVitest(
'test',
[], // CLI filters
{}, // override test config
{}, // override Vite config
{}, // custom Vitest options
)
const testModules = vitest.state.getTestModules()
for (const testModule of testModules) {
console.log(testModule.moduleId, 'results', testModule.result())
}
```

::: tip
[`TestModule`](/advanced/reporters#TestModule), [`TestSuite`](/advanced/reporters#TestSuite) and [`TestCase`](/advanced/reporters#TestCase) APIs are not experimental and follow SemVer since Vitest 2.1.
:::

## `createVitest`

`createVitest` method doesn't validate that required packages are installed. This method also doesn't respect `config.standalone` or `config.mergeReports`. Vitest also won't be closed automatically even if `watch` is disabled.

```ts
import { createVitest } from 'vitest/node'

const vitest = await createVitest(
'test',
{}, // override test config
{}, // override Vite config
{}, // custom Vitest options
)

// called when `vitest.cancelCurrentRun()` is invoked
vitest.onCancel(() => {})
// called during `vitest.close()` call
vitest.onClose(() => {})
// called when Vitest reruns test files
vitest.onTestsRerun((files) => {})

try {
// this will set process.exitCode to 1 if tests failed
await vitest.start(['my-filter'])
}
catch (err) {
// this can throw
// "FilesNotFoundError" if no files were found
// "GitNotFoundError" if `--changed` is enabled and repository is not initialized
}
finally {
await vitest.close()
}
```
6 changes: 3 additions & 3 deletions docs/advanced/pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ export default defineConfig({
The file specified in `pool` option should export a function (can be async) that accepts `Vitest` interface as its first option. This function needs to return an object matching `ProcessPool` interface:

```ts
import { ProcessPool, WorkspaceProject } from 'vitest/node'
import { ProcessPool, TestSpecification } from 'vitest/node'

export interface ProcessPool {
name: string
runTests: (files: [project: WorkspaceProject, testFile: string][], invalidates?: string[]) => Promise<void>
collectTests: (files: [project: WorkspaceProject, testFile: string][], invalidates?: string[]) => Promise<void>
runTests: (files: TestSpecification[], invalidates?: string[]) => Promise<void>
collectTests: (files: TestSpecification[], invalidates?: string[]) => Promise<void>
close?: () => Promise<void>
}
```
Expand Down
Loading

0 comments on commit 171041a

Please sign in to comment.