From 69377dd94c481704d21a597e08d9fd695936c77e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 12 Jan 2025 00:17:27 +0900 Subject: [PATCH 01/15] docs: tweak alias example (#7206) --- guide/mocking.md | 1 + 1 file changed, 1 insertion(+) diff --git a/guide/mocking.md b/guide/mocking.md index 6793d76d..dab36728 100644 --- a/guide/mocking.md +++ b/guide/mocking.md @@ -177,6 +177,7 @@ Vitest supports mocking Vite [virtual modules](https://vitejs.dev/guide/api-plug ```ts [vitest.config.js] import { defineConfig } from 'vitest/config' +import { resolve } from 'node:path' export default defineConfig({ test: { alias: { From 4bf638bf0dc5ecdc157583d5295d8a8aac9c2153 Mon Sep 17 00:00:00 2001 From: Robin <37191683+OrbisK@users.noreply.github.com> Date: Sat, 11 Jan 2025 16:18:19 +0100 Subject: [PATCH 02/15] docs(locators): update provider config link (#7211) --- guide/browser/locators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/browser/locators.md b/guide/browser/locators.md index d9b33c35..b4386215 100644 --- a/guide/browser/locators.md +++ b/guide/browser/locators.md @@ -7,7 +7,7 @@ outline: [2, 3] A locator is a representation of an element or a number of elements. Every locator is defined by a string called a selector. Vitest abstracts this selector by providing convenient methods that generate those selectors behind the scenes. -The locator API uses a fork of [Playwright's locators](https://playwright.dev/docs/api/class-locator) called [Ivya](https://npmjs.com/ivya). However, Vitest provides this API to every [provider](/guide/browser/#provider-configuration). +The locator API uses a fork of [Playwright's locators](https://playwright.dev/docs/api/class-locator) called [Ivya](https://npmjs.com/ivya). However, Vitest provides this API to every [provider](/guide/browser/config.html#browser-provider). ## getByRole From 357d7b64d5b5d96bd2f029df9140cab3737a93a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?= Date: Sun, 12 Jan 2025 17:32:11 +0100 Subject: [PATCH 03/15] docs: fix browser testerhtmlpath link (#7220) --- guide/browser/config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/browser/config.md b/guide/browser/config.md index 5d4cfcfc..38262d2f 100644 --- a/guide/browser/config.md +++ b/guide/browser/config.md @@ -301,7 +301,7 @@ export interface BrowserScript { - **Default:** `[]` ::: danger -This API is deprecated an will be removed in Vitest 4. Please, use [`browser.testerHtmlPath`](#browser-testerHtmlPath) field instead. +This API is deprecated an will be removed in Vitest 4. Please, use [`browser.testerHtmlPath`](#browser-testerhtmlpath) field instead. ::: Custom scripts that should be injected into the tester HTML before the tests environment is initiated. This is useful to inject polyfills required for Vitest browser implementation. It is recommended to use [`setupFiles`](#setupfiles) in almost all cases instead of this. From 9ec1afbefb2413ce2ea01ddd41c89a571670d913 Mon Sep 17 00:00:00 2001 From: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:49:57 +0530 Subject: [PATCH 04/15] docs: added more description about connecting UI with right mode. (#7119) --- guide/ui.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guide/ui.md b/guide/ui.md index 156588de..3219a7b9 100644 --- a/guide/ui.md +++ b/guide/ui.md @@ -18,6 +18,10 @@ vitest --ui Then you can visit the Vitest UI at `http://localhost:51204/__vitest__/` +::: warning +The UI is interactive and requires a running Vite server, so make sure to run Vitest in `watch` mode (the default). Alternatively, you can generate a static HTML report that looks identical to the Vitest UI by specifying `html` in config's `reporters` option. +::: + Vitest UI Vitest UI From 6f11d56740545c66266dd8ccec5bdeace9a87f73 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 14 Jan 2025 06:42:38 +0100 Subject: [PATCH 05/15] fix: cleanup `vitest/reporters` entrypoint (#7241) --- advanced/reporters.md | 6 ++---- guide/migration.md | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/advanced/reporters.md b/advanced/reporters.md index 494c1fba..2a486d62 100644 --- a/advanced/reporters.md +++ b/advanced/reporters.md @@ -36,7 +36,7 @@ export default class CustomReporter extends BaseReporter { Or implement the `Reporter` interface: ```ts [custom-reporter.js] -import { Reporter } from 'vitest/reporters' +import type { Reporter } from 'vitest/node' export default class CustomReporter implements Reporter { onCollected() { @@ -65,9 +65,7 @@ Instead of using the tasks that reporters receive, it is recommended to use the You can get access to this API by calling `vitest.state.getReportedEntity(runnerTask)`: ```ts twoslash -import type { Vitest } from 'vitest/node' -import type { RunnerTestFile } from 'vitest' -import type { Reporter, TestModule } from 'vitest/reporters' +import type { Reporter, RunnerTestFile, TestModule, Vitest } from 'vitest/node' class MyReporter implements Reporter { private vitest!: Vitest diff --git a/guide/migration.md b/guide/migration.md index d57569fd..0e18d944 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -81,6 +81,10 @@ The [`resolveConfig`](/advanced/api/#resolveconfig) is now more useful. Instead This function is not used internally and exposed exclusively as a public API. +### Cleaned up `vitest/reporters` types API {#cleaned-up-vitest-reporters-types} + +The `vitest/reporters` entrypoint now only exports reporters implementations and options types. If you need access to `TestCase`/`TestSuite` and other task related types, import them additionally from `vitest/node`. + ## Migrating to Vitest 2.0 {#vitest-2} ### Default Pool is `forks` From d73908f81afe7e2e74113e56041ff8840672e609 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 14 Jan 2025 06:43:30 +0100 Subject: [PATCH 06/15] fix(workspace): `extends: true` correctly inherits all root config properties (#7232) --- guide/workspace.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/workspace.md b/guide/workspace.md index 579ca2c0..c53e08e1 100644 --- a/guide/workspace.md +++ b/guide/workspace.md @@ -44,7 +44,7 @@ export default defineConfig({ Vitest will treat every folder in `packages` as a separate project even if it doesn't have a config file inside. If this glob pattern matches any file it will be considered a Vitest config even if it doesn't have a `vitest` in its name. ::: warning -Vitest does not treat the root `vitest.config` file as a workspace project unless it is explicitly specified in the workspace configuration. Consequently, the root configuration will only influence global options such as `reporters` and `coverage`. +Vitest does not treat the root `vitest.config` file as a workspace project unless it is explicitly specified in the workspace configuration. Consequently, the root configuration will only influence global options such as `reporters` and `coverage`. Note that Vitest will always run certain plugin hooks, like `apply`, `config`, `configResolved` or `configureServer`, specified in the root config file. Vitest also uses the same plugins to execute global setups, workspace files and custom coverage provider. ::: You can also reference projects with their config files: @@ -233,7 +233,7 @@ bun test --project e2e --project unit ## Configuration -None of the configuration options are inherited from the root-level config file. You can create a shared config file and merge it with the project config yourself: +None of the configuration options are inherited from the root-level config file, even if the workspace is defined inside that config and not in a separate `vitest.workspace` file. You can create a shared config file and merge it with the project config yourself: ```ts [packages/a/vitest.config.ts] import { defineProject, mergeConfig } from 'vitest/config' From dc3f007c37a718074d6b3b367159c7a198c04c0d Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 14 Jan 2025 16:46:44 +0100 Subject: [PATCH 07/15] feat: introduce the new reporter API (#7069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ari Perkkiö --- .vitepress/config.ts | 4 + advanced/api/reporters.md | 311 +++++++++++++++++++++++++++++ advanced/api/test-case.md | 97 ++++----- advanced/api/test-collection.md | 10 +- advanced/api/test-module.md | 45 ++--- advanced/api/test-specification.md | 8 + advanced/api/test-suite.md | 71 ++++--- advanced/metadata.md | 31 ++- api/index.md | 10 + 9 files changed, 446 insertions(+), 141 deletions(-) create mode 100644 advanced/api/reporters.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 991389fe..2ead4fa0 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -339,6 +339,10 @@ export default ({ mode }: { mode: string }) => { text: 'Runner API', link: '/advanced/runner', }, + { + text: 'Reporters API', + link: '/advanced/api/reporters', + }, { text: 'Task Metadata', link: '/advanced/metadata', diff --git a/advanced/api/reporters.md b/advanced/api/reporters.md new file mode 100644 index 00000000..7978a1d8 --- /dev/null +++ b/advanced/api/reporters.md @@ -0,0 +1,311 @@ +# Reporters + +::: warning +This is an advanced API. If you just want to configure built-in reporters, read the ["Reporters"](/guide/reporters) guide. +::: + +Vitest has its own test run lifecycle. These are represented by reporter's methods: + +- [`onInit`](#oninit) +- [`onTestRunStart`](#ontestrunstart) + - [`onTestModuleQueued`](#ontestmodulequeued) + - [`onTestModuleCollected`](#ontestmodulecollected) + - [`onTestModuleStart`](#ontestmodulestart) + - [`onTestSuiteReady`](#ontestsuiteready) + - [`onHookStart(beforeAll)`](#onhookstart) + - [`onHookEnd(beforeAll)`](#onhookend) + - [`onTestCaseReady`](#ontestcaseready) + - [`onHookStart(beforeEach)`](#onhookstart) + - [`onHookEnd(beforeEach)`](#onhookend) + - [`onHookStart(afterEach)`](#onhookstart) + - [`onHookEnd(afterEach)`](#onhookend) + - [`onTestCaseResult`](#ontestcaseresult) + - [`onHookStart(afterAll)`](#onhookstart) + - [`onHookEnd(afterAll)`](#onhookend) + - [`onTestSuiteResult`](#ontestsuiteresult) + - [`onTestModuleEnd`](#ontestmoduleend) +- [`onTestRunEnd`](#ontestrunend) + +Tests and suites within a single module will be reported in order unless they were skipped. All skipped tests are reported at the end of suite/module. + +Note that since test modules can run in parallel, Vitest will report them in parallel. + +This guide lists all supported reporter methods. However, don't forget that instead of creating your own reporter, you can [extend existing one](/advanced/reporters) instead: + +```ts [custom-reporter.js] +import { BaseReporter } from 'vitest/reporters' + +export default class CustomReporter extends BaseReporter { + onTestRunEnd(testModules, errors) { + console.log(testModule.length, 'tests finished running') + super.onTestRunEnd(testModules, errors) + } +} +``` + +## onInit + +```ts +function onInit(vitest: Vitest): Awaitable +``` + +This method is called when [Vitest](/advanced/api/vitest) was initiated or started, but before the tests were filtered. + +::: info +Internally this method is called inside [`vitest.start`](/advanced/api/vitest#start), [`vitest.init`](/advanced/api/vitest#init) or [`vitest.mergeReports`](/advanced/api/vitest#mergereports). If you are using programmatic API, make sure to call either one dependning on your needs before calling [`vitest.runTestSpecifications`](/advanced/api/vitest#runtestspecifications), for example. Built-in CLI will always run methods in correct order. +::: + +Note that you can also get access to `vitest` instance from test cases, suites and test modules via a [`project`](/advanced/api/test-project) property, but it might also be useful to store a reference to `vitest` in this method. + +::: details Example +```ts +import type { Reporter, TestSpecification, Vitest } from 'vitest/node' + +class MyReporter implements Reporter { + private vitest!: Vitest + + onInit(vitest: Vitest) { + this.vitest = vitest + } + + onTestRunStart(specifications: TestSpecification[]) { + console.log( + specifications.length, + 'test files will run in', + this.vitest.config.root, + ) + } +} + +export default new MyReporter() +``` +::: + +## onTestRunStart + +```ts +function onTestRunStart( + specifications: TestSpecification[] +): Awaitable +``` + +This method is called when a new test run has started. It receives an array of [test specifications](/advanced/api/test-specification) scheduled to run. This array is readonly and available only for information purposes. + +If Vitest didn't find any test files to run, this event will be invoked with an empty array, and then [`onTestRunEnd`](#ontestrunend) will be called immediately after. + +::: details Example +```ts +import type { Reporter, TestSpecification } from 'vitest/node' + +class MyReporter implements Reporter { + onTestRunStart(specifications: TestSpecification[]) { + console.log(specifications.length, 'test files will run') + } +} + +export default new MyReporter() +``` +::: + +::: tip DEPRECATION NOTICE +This method was added in Vitest 3, replacing `onPathsCollected` and `onSpecsCollected`, both of which are now deprecated. +::: + +## onTestRunEnd + +```ts +function onTestRunEnd( + testModules: ReadonlyArray, + unhandledErrors: ReadonlyArray, + reason: TestRunEndReason +): Awaitable +``` + +This method is called after all tests have finished running and the coverage merged all reports, if it's enabled. Note that you can get the coverage information in [`onCoverage`](#oncoverage) hook. + +It receives a readonly list of test modules. You can iterate over it via a [`testModule.children`](/advanced/api/test-collection) property to report the state and errors, if any. + +The second argument is a readonly list of unhandled errors that Vitest wasn't able to attribute to any test. These can happen outside of the test run because of an error in a plugin, or inside the test run as a side-effect of a non-awaited function (for example, a timeout that threw an error after the test has finished running). + +The third argument indicated why the test run was finished: + +- `passed`: test run was finished normally and there are no errors +- `failed`: test run has at least one error (due to a syntax error during collection or an actual error during test execution) +- `interrupted`: test was interruped by [`vitest.cancelCurrentRun`](/advanced/api/vitest#cancelcurrentrun) call or `Ctrl+C` was pressed in the terminal (note that it's still possible to have failed tests in this case) + +If Vitest didn't find any test files to run, this event will be invoked with empty arrays of modules and errors, and the state will depend on the value of [`config.passWithNoTests`](/config/#passwithnotests). + +::: details Example +```ts +import type { + Reporter, + SerializedError, + TestModule, + TestRunEndReason, + TestSpecification +} from 'vitest/node' + +class MyReporter implements Reporter { + onTestRunEnd( + testModules: ReadonlyArray, + unhandledErrors: ReadonlyArray, + reason: TestRunEndReason, + ) { + if (reason === 'passed') { + testModules.forEach(module => console.log(module.moduleId, 'succeeded')) + } + else if (reason === 'failed') { + // note that this will skip possible errors in suites + // you can get them from testSuite.errors() + for (const testCase of testModules.children.allTests()) { + if (testCase.result().state === 'failed') { + console.log(testCase.fullName, 'in', testCase.module.moduleId, 'failed') + console.log(testCase.result().errors) + } + } + } + else { + console.log('test run was interrupted, skipping report') + } + } +} + +export default new MyReporter() +``` +::: + +::: tip DEPRECATION NOTICE +This method was added in Vitest 3, replacing `onFinished`, which is now deprecated. +::: + +## onCoverage + +```ts +function onCoverage(coverage: unknown): Awaitable +``` + +This hook is called after coverage results have been processed. Coverage provider's reporters are called after this hook. The typings of `coverage` depends on the `coverage.provider`. For Vitest's default built-in providers you can import the types from `istanbul-lib-coverage` package: + +```ts +import type { CoverageMap } from 'istanbul-lib-coverage' + +declare function onCoverage(coverage: CoverageMap): Awaitable +``` + +If Vitest didn't perform any coverage, this hook is not called. + +## onTestModuleQueued + +```ts +function onTestModuleQueued(testModule: TestModule): Awaitable +``` + +This method is called right before Vitest imports the setup file and the test module itself. This means that `testModule` will have no [`children`](/advanced/api/test-suite#children) yet, but you can start reporting it as the next test to run. + +## onTestModuleCollected + +```ts +function onTestModuleCollected(testModule: TestModule): Awaitable +``` + +This method is called when all tests inside the file were collected, meaning [`testModule.children`](/advanced/api/test-suite#children) collection is populated, but tests don't have any results yet. + +## onTestModuleStart + +```ts +function onTestModuleStart(testModule: TestModule): Awaitable +``` + +This method is called right after [`onTestModuleCollected`](#ontestmodulecollected) unless Vitest runs in collection mode ([`vitest.collect()`](/advanced/api/vitest#collect) or `vitest collect` in the CLI), in this case it will not be called at all because there are no tests to run. + +## onTestModuleEnd + +```ts +function onTestModuleEnd(testModule: TestModule): Awaitable +``` + +This method is called when every test in the module finished running. This means, every test inside [`testModule.children`](/advanced/api/test-suite#children) will have a `test.result()` that is not equal to `pending`. + +## onHookStart + +```ts +function onHookStart(context: ReportedHookContext): Awaitable +``` + +This method is called when any of these hooks have started running: + +- `beforeAll` +- `afterAll` +- `beforeEach` +- `afterEach` + +If `beforeAll` or `afterAll` are started, the `entity` will be either [`TestSuite`](/advanced/api/test-suite) or [`TestModule`](/advanced/api/test-module). + +If `beforeEach` or `afterEach` are started, the `entity` will always be [`TestCase`](/advanced/api/test-case). + +::: warning +`onHookStart` method will not be called if the hook did not run during the test run. +::: + +## onHookEnd + +```ts +function onHookEnd(context: ReportedHookContext): Awaitable +``` + +This method is called when any of these hooks have finished running: + +- `beforeAll` +- `afterAll` +- `beforeEach` +- `afterEach` + +If `beforeAll` or `afterAll` have finished, the `entity` will be either [`TestSuite`](/advanced/api/test-suite) or [`TestModule`](/advanced/api/test-module). + +If `beforeEach` or `afterEach` have finished, the `entity` will always be [`TestCase`](/advanced/api/test-case). + +::: warning +`onHookEnd` method will not be called if the hook did not run during the test run. +::: + +## onTestSuiteReady + +```ts +function onTestSuiteReady(testSuite: TestSuite): Awaitable +``` + +This method is called before the suite starts to run its tests. This method is also called if the suite was skipped. + +If the file doesn't have any suites, this method will not be called. Consider using `onTestModuleStart` to cover this use case. + +## onTestSuiteResult + +```ts +function onTestSuiteResult(testSuite: TestSuite): Awaitable +``` + +This method is called after the suite has finished running tests. This method is also called if the suite was skipped. + +If the file doesn't have any suites, this method will not be called. Consider using `onTestModuleEnd` to cover this use case. + +## onTestCaseReady + +```ts +function onTestCaseReady(testCase: TestCase): Awaitable +``` + +This method is called before the test starts to run or it was skipped. Note that `beforeEach` and `afterEach` hooks are considered part of the test because they can influence the result. + +::: warning +Notice that it's possible to have [`testCase.result()`](/advanced/api/test-case#result) with `passed` or `failed` state already when `onTestCaseReady` is called. This can happen if test was running too fast and both `onTestCaseReady` and `onTestCaseResult` were scheduled to run in the same microtask. +::: + +## onTestCaseResult + +```ts +function onTestCaseResult(testCase: TestCase): Awaitable +``` + +This method is called when the test has finished running or was just skipped. Note that this will be called after the `afterEach` hook is finished, if there are any. + +At this point, [`testCase.result()`](/advanced/api/test-case#result) will have non-pending state. diff --git a/advanced/api/test-case.md b/advanced/api/test-case.md index 3f504668..40712091 100644 --- a/advanced/api/test-case.md +++ b/advanced/api/test-case.md @@ -10,31 +10,6 @@ if (task.type === 'test') { } ``` -::: warning -We are planning to introduce a new Reporter API that will be using this API by default. For now, the Reporter API uses [runner tasks](/advanced/runner#tasks), but you can still access `TestCase` via `vitest.state.getReportedEntity` method: - -```ts -import type { RunnerTestFile, TestModule, Vitest } from 'vitest/node' - -class Reporter { - private vitest!: Vitest - - onInit(vitest: Vitest) { - this.vitest = vitest - } - - onFinished(files: RunnerTestFile[]) { - for (const file of files) { - const testModule = this.vitest.getReportedEntity(file) as TestModule - for (const test of testModule.children.allTests()) { - console.log(test) // TestCase - } - } - } -} -``` -::: - ## project This references the [`TestProject`](/advanced/api/test-project) that the test belongs to. @@ -124,12 +99,13 @@ Parent [suite](/advanced/api/test-suite). If the test was called directly inside ```ts interface TaskOptions { - each: boolean | undefined - concurrent: boolean | undefined - shuffle: boolean | undefined - retry: number | undefined - repeats: number | undefined - mode: 'run' | 'only' | 'skip' | 'todo' + readonly each: boolean | undefined + readonly fails: boolean | undefined + readonly concurrent: boolean | undefined + readonly shuffle: boolean | undefined + readonly retry: number | undefined + readonly repeats: number | undefined + readonly mode: 'run' | 'only' | 'skip' | 'todo' } ``` @@ -143,14 +119,6 @@ function ok(): boolean Checks if the test did not fail the suite. If the test is not finished yet or was skipped, it will return `true`. -## skipped - -```ts -function skipped(): boolean -``` - -Checks if the test was skipped during collection or dynamically with `ctx.skip()`. - ## meta ```ts @@ -174,10 +142,23 @@ If the test did not finish running yet, the meta will be an empty object. ## result ```ts -function result(): TestResult | undefined +function result(): TestResult ``` -Test results. It will be `undefined` if test is skipped during collection, not finished yet or was just collected. +Test results. If test is not finished yet or was just collected, it will be equal to `TestResultPending`: + +```ts +export interface TestResultPending { + /** + * The test was collected, but didn't finish running yet. + */ + readonly state: 'pending' + /** + * Pending tests have no errors. + */ + readonly errors: undefined +} +``` If the test was skipped, the return value will be `TestResultSkipped`: @@ -187,15 +168,15 @@ interface TestResultSkipped { * The test was skipped with `skip` or `todo` flag. * You can see which one was used in the `options.mode` option. */ - state: 'skipped' + readonly state: 'skipped' /** * Skipped tests have no errors. */ - errors: undefined + readonly errors: undefined /** * A custom note passed down to `ctx.skip(note)`. */ - note: string | undefined + readonly note: string | undefined } ``` @@ -210,26 +191,26 @@ interface TestResultFailed { /** * The test failed to execute. */ - state: 'failed' + readonly state: 'failed' /** * Errors that were thrown during the test execution. */ - errors: TestError[] + readonly errors: ReadonlyArray } ``` -If the test passed, the retunr value will be `TestResultPassed`: +If the test passed, the return value will be `TestResultPassed`: ```ts interface TestResultPassed { /** * The test passed successfully. */ - state: 'passed' + readonly state: 'passed' /** * Errors that were thrown during the test execution. */ - errors: TestError[] | undefined + readonly errors: ReadonlyArray | undefined } ``` @@ -250,32 +231,36 @@ interface TestDiagnostic { /** * If the duration of the test is above `slowTestThreshold`. */ - slow: boolean + readonly 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 + readonly heap: number | undefined /** * The time it takes to execute the test in ms. */ - duration: number + readonly duration: number /** * The time in ms when the test started. */ - startTime: number + readonly startTime: number /** * The amount of times the test was retried. */ - retryCount: number + readonly 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 + readonly repeatCount: number /** * If test passed on a second retry. */ - flaky: boolean + readonly flaky: boolean } ``` + +::: info +`diagnostic()` will return `undefined` if the test was not scheduled to run yet. +::: diff --git a/advanced/api/test-collection.md b/advanced/api/test-collection.md index 974f37db..988f9d96 100644 --- a/advanced/api/test-collection.md +++ b/advanced/api/test-collection.md @@ -57,16 +57,14 @@ for (const suite of module.children.allSuites()) { ## allTests ```ts -function allTests( - state?: TestResult['state'] | 'running' -): Generator +function allTests(state?: TestState): Generator ``` Filters all tests that are part of this collection and its children. ```ts for (const test of module.children.allTests()) { - if (!test.result()) { + if (test.result().state === 'pending') { console.log('test', test.fullName, 'did not finish') } } @@ -77,9 +75,7 @@ You can pass down a `state` value to filter tests by the state. ## tests ```ts -function tests( - state?: TestResult['state'] | 'running' -): Generator +function tests(state?: TestState): Generator ``` Filters only the tests that are part of this collection. You can pass down a `state` value to filter tests by the state. diff --git a/advanced/api/test-module.md b/advanced/api/test-module.md index 07ae1230..9a39dc0e 100644 --- a/advanced/api/test-module.md +++ b/advanced/api/test-module.md @@ -10,34 +10,27 @@ if (task.type === 'module') { } ``` -The `TestModule` inherits all methods and properties from the [`TestSuite`](/advanced/api/test-module). This guide will only list methods and properties unique to the `TestModule` +::: warning Extending Suite Methods +The `TestModule` class inherits all methods and properties from the [`TestSuite`](/advanced/api/test-suite). This guide will only list methods and properties unique to the `TestModule`. +::: -::: warning -We are planning to introduce a new Reporter API that will be using this API by default. For now, the Reporter API uses [runner tasks](/advanced/runner#tasks), but you can still access `TestModule` via `vitest.state.getReportedEntity` method: +## moduleId -```ts -import type { RunnerTestFile, TestModule, Vitest } from 'vitest/node' +This is usually an absolute unix file path (even on Windows). It can be a virtual id if the file is not on the disk. This value corresponds to Vite's `ModuleGraph` id. -class Reporter { - private vitest!: Vitest +```ts +'C:/Users/Documents/project/example.test.ts' // ✅ +'/Users/mac/project/example.test.ts' // ✅ +'C:\\Users\\Documents\\project\\example.test.ts' // ❌ +``` - onInit(vitest: Vitest) { - this.vitest = vitest - } +## state - onFinished(files: RunnerTestFile[]) { - for (const file of files) { - const testModule = this.vitest.state.getReportedEntity(file) as TestModule - console.log(testModule) // TestModule - } - } -} +```ts +function state(): TestModuleState ``` -::: -## moduleId - -This is usually an absolute unix file path (even on Windows). It can be a virtual id if the file is not on the disk. This value corresponds to Vite's `ModuleGraph` id. +Works the same way as [`testSuite.state()`](/advanced/api/test-suite#state), but can also return `queued` if module wasn't executed yet. ## diagnostic @@ -52,23 +45,23 @@ interface ModuleDiagnostic { /** * The time it takes to import and initiate an environment. */ - environmentSetupDuration: number + readonly environmentSetupDuration: number /** * The time it takes Vitest to setup test harness (runner, mocks, etc.). */ - prepareDuration: number + readonly prepareDuration: number /** * The time it takes to import the test module. * This includes importing everything in the module and executing suite callbacks. */ - collectDuration: number + readonly collectDuration: number /** * The time it takes to import the setup module. */ - setupDuration: number + readonly setupDuration: number /** * Accumulated duration of all tests and hooks in the module. */ - duration: number + readonly duration: number } ``` diff --git a/advanced/api/test-specification.md b/advanced/api/test-specification.md index 3fefba0c..b6e0e91e 100644 --- a/advanced/api/test-specification.md +++ b/advanced/api/test-specification.md @@ -13,6 +13,10 @@ const specification = project.createSpecification( `createSpecification` expects resolved module ID. It doesn't auto-resolve the file or check that it exists on the file system. +## taskId + +[Test module's](/advanced/api/test-suite#id) identifier. + ## project This references the [`TestProject`](/advanced/api/test-project) that the test module belongs to. @@ -27,6 +31,10 @@ The ID of the module in Vite's module graph. Usually, it's an absolute file path 'C:\\Users\\Documents\\project\\example.test.ts' // ❌ ``` +## testModule + +Instance of [`TestModule`](/advanced/api/test-module) assosiated with the specification. If test wasn't queued yet, this will be `undefined`. + ## pool experimental {#pool} The [`pool`](/config/#pool) in which the test module will run. diff --git a/advanced/api/test-suite.md b/advanced/api/test-suite.md index 85673c43..b0638ec7 100644 --- a/advanced/api/test-suite.md +++ b/advanced/api/test-suite.md @@ -10,31 +10,6 @@ if (task.type === 'suite') { } ``` -::: warning -We are planning to introduce a new Reporter API that will be using this API by default. For now, the Reporter API uses [runner tasks](/advanced/runner#tasks), but you can still access `TestSuite` via `vitest.state.getReportedEntity` method: - -```ts -import type { RunnerTestFile, TestModule, Vitest } from 'vitest/node' - -class Reporter { - private vitest!: Vitest - - onInit(vitest: Vitest) { - this.vitest = vitest - } - - onFinished(files: RunnerTestFile[]) { - for (const file of files) { - const testModule = this.vitest.state.getReportedEntity(file) as TestModule - for (const suite of testModule.children.allSuites()) { - console.log(suite) // TestSuite - } - } - } -} -``` -::: - ## project This references the [`TestProject`](/advanced/api/test-project) that the test belongs to. @@ -125,12 +100,13 @@ Parent suite. If the suite was called directly inside the [module](/advanced/api ```ts interface TaskOptions { - each: boolean | undefined - concurrent: boolean | undefined - shuffle: boolean | undefined - retry: number | undefined - repeats: number | undefined - mode: 'run' | 'only' | 'skip' | 'todo' + readonly each: boolean | undefined + readonly fails: boolean | undefined + readonly concurrent: boolean | undefined + readonly shuffle: boolean | undefined + readonly retry: number | undefined + readonly repeats: number | undefined + readonly mode: 'run' | 'only' | 'skip' | 'todo' } ``` @@ -153,7 +129,21 @@ for (const task of suite.children) { ``` ::: warning -Note that `suite.children` will only iterate the first level of nesting, it won't go deeper. +Note that `suite.children` will only iterate the first level of nesting, it won't go deeper. If you need to iterate over all tests or suites, use [`children.allTests()`](/advanced/api/test-collection#alltests) or [`children.allSuites()`](/advanced/api/test-collection#allsuites). If you need to iterate over everything, use recursive function: + +```ts +function visit(collection: TestCollection) { + for (const task of collection) { + if (task.type === 'suite') { + // report a suite + visit(task.children) + } + else { + // report a test + } + } +} +``` ::: ## ok @@ -164,13 +154,22 @@ function ok(): boolean Checks if the suite has any failed tests. This will also return `false` if suite failed during collection. In that case, check the [`errors()`](#errors) for thrown errors. -## skipped +## state ```ts -function skipped(): boolean +function state(): TestSuiteState ``` -Checks if the suite was skipped during collection. +Checks the running state of the suite. Possible return values: + +- **pending**: the tests in this suite did not finish running yet. +- **failed**: this suite has failed tests or they couldn't be collected. If [`errors()`](#errors) is not empty, it means the suite failed to collect tests. +- **passed**: every test inside this suite has passed. +- **skipped**: this suite was skipped during collection. + +::: warning +Note that [test module](/advanced/api/test-module) also has a `state` method that returns the same values, but it can also return an additional `queued` state if the module wasn't executed yet. +::: ## errors @@ -189,5 +188,5 @@ describe('collection failed', () => { ``` ::: warning -Note that errors are serialized into simple object: `instanceof Error` will always return `false`. +Note that errors are serialized into simple objects: `instanceof Error` will always return `false`. ::: diff --git a/advanced/metadata.md b/advanced/metadata.md index 6efd2762..883eb70c 100644 --- a/advanced/metadata.md +++ b/advanced/metadata.md @@ -20,26 +20,23 @@ test('custom', ({ task }) => { }) ``` -Once a test is completed, Vitest will send a task including the result and `meta` to the Node.js process using RPC. To intercept and process this task, you can utilize the `onTaskUpdate` method available in your reporter implementation: +Once a test is completed, Vitest will send a task including the result and `meta` to the Node.js process using RPC, and then report it in `onTestCaseResult` and other hooks that have access to tasks. To process this test case, you can utilize the `onTestCaseResult` method available in your reporter implementation: ```ts [custom-reporter.js] +import type { Reporter, TestCase, TestModule } from 'vitest/node' + export default { - // you can intercept packs if needed - onTaskUpdate(packs) { - const [id, result, meta] = packs[0] + onTestCaseResult(testCase: TestCase) { + // custom === 'some-custom-handler' ✅ + const { custom } = testCase.meta() }, - // meta is located on every task inside "onFinished" - onFinished(files) { - files[0].meta.done === true - files[0].tasks[0].meta.custom === 'some-custom-handler' + onTestRunEnd(testModule: TestModule) { + testModule.meta().done === true + testModule.children.at(0).meta().custom === 'some-custom-handler' } -} +} satisfies Reporter ``` -::: warning -Vitest can send several tasks at the same time if several tests are completed in a short period of time. -::: - ::: danger BEWARE Vitest uses different methods to communicate with the Node.js process. @@ -56,9 +53,11 @@ You can also get this information from Vitest state when tests finished running: ```ts const vitest = await createVitest('test') -await vitest.start() -vitest.state.getFiles()[0].meta.done === true -vitest.state.getFiles()[0].tasks[0].meta.custom === 'some-custom-handler' +const { testModules } = await vitest.start() + +const testModule = testModules[0] +testModule.meta().done === true +testModule.children.at(0).meta().custom === 'some-custom-handler' ``` It's also possible to extend type definitions when using TypeScript: diff --git a/api/index.md b/api/index.md index a269d94c..fadfe142 100644 --- a/api/index.md +++ b/api/index.md @@ -1179,6 +1179,16 @@ test('performs an organization query', async () => { ::: tip This hook is always called in reverse order and is not affected by [`sequence.hooks`](/config/#sequence-hooks) option. + + +Note that this hook is not called if test was skipped with a dynamic `ctx.skip()` call: + +```ts{2} +test('skipped dynamically', (t) => { + onTestFinished(() => {}) // not called + t.skip() +}) +``` ::: ### onTestFailed From a22b5457db28ccc3bbf05ec02c263de91463d896 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 15 Jan 2025 16:49:42 +0900 Subject: [PATCH 08/15] feat: add `describe.for` (#7253) --- api/index.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/api/index.md b/api/index.md index fadfe142..729ca757 100644 --- a/api/index.md +++ b/api/index.md @@ -948,6 +948,11 @@ describe.todo('unimplemented suite') - **Alias:** `suite.each` +::: tip +While `describe.each` is provided for Jest compatibility, +Vitest also has [`describe.for`](#describe-for) which simplifies argument types and aligns with [`test.for`](#test-for). +::: + Use `describe.each` if you have more than one test that depends on the same data. ```ts @@ -998,6 +1003,37 @@ describe.each` You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). ::: +### describe.for + +- **Alias:** `suite.for` + +The difference from `describe.each` is how array case is provided in the arguments. +Other non array case (including template string usage) works exactly same. + +```ts +// `each` spreads array case +describe.each([ + [1, 1, 2], + [1, 2, 3], + [2, 1, 3], +])('add(%i, %i) -> %i', (a, b, expected) => { // [!code --] + test('test', () => { + expect(a + b).toBe(expected) + }) +}) + +// `for` doesn't spread array case +describe.for([ + [1, 1, 2], + [1, 2, 3], + [2, 1, 3], +])('add(%i, %i) -> %i', ([a, b, expected]) => { // [!code ++] + test('test', () => { + expect(a + b).toBe(expected) + }) +}) +``` + ## Setup and Teardown These functions allow you to hook into the life cycle of tests to avoid repeating setup and teardown code. They apply to the current context: the file if they are used at the top-level or the current suite if they are inside a `describe` block. These hooks are not called, when you are running Vitest as a type checker. From c46f4f3b136e06a89404d210eaa32ce6587800a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Lopes?= Date: Wed, 15 Jan 2025 10:04:30 -0300 Subject: [PATCH 09/15] fix(browser): add instance validation to resolve coverage error (#7231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ari Perkkiö --- guide/debugging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/debugging.md b/guide/debugging.md index 9d0dc205..0e0fd934 100644 --- a/guide/debugging.md +++ b/guide/debugging.md @@ -58,8 +58,8 @@ export default defineConfig({ inspectBrk: true, fileParallelism: false, browser: { - name: 'chromium', provider: 'playwright', + instances: [{ browser: 'chromium' }] }, }, }) From f37f50e1a7c1c88907ff9c5608eb622540eead08 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 15 Jan 2025 15:09:12 +0100 Subject: [PATCH 10/15] feat(api): add onBrowserInit event (#7255) --- advanced/api/reporters.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/advanced/api/reporters.md b/advanced/api/reporters.md index 7978a1d8..06ebd06a 100644 --- a/advanced/api/reporters.md +++ b/advanced/api/reporters.md @@ -81,6 +81,14 @@ export default new MyReporter() ``` ::: +## onBrowserInit experimental {#onbrowserinit} + +```ts +function onBrowserInit(project: TestProject): Awaitable +``` + +This method is called when the browser instance is initiated. It receives an instance of the project for which the browser is initiated. `project.browser` will always be defined when this method is called. + ## onTestRunStart ```ts From 372aba690a16e2350815f06f4a4052c18fad4941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Wed, 15 Jan 2025 18:40:22 +0200 Subject: [PATCH 11/15] fix(coverage)!: always exclude test files (#7254) --- config/index.md | 3 ++- guide/migration.md | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config/index.md b/config/index.md index f4fbdf43..01a5efe5 100644 --- a/config/index.md +++ b/config/index.md @@ -1351,7 +1351,8 @@ export default defineConfig({ ``` ::: tip NOTE -Vitest automatically adds test files `include` patterns to the default value of `coverage.exclude`. +Vitest automatically adds test files `include` patterns to the `coverage.exclude`. +It's not possible to show coverage of test files. ::: #### coverage.all diff --git a/guide/migration.md b/guide/migration.md index 0e18d944..8d3499cd 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -85,6 +85,10 @@ This function is not used internally and exposed exclusively as a public API. The `vitest/reporters` entrypoint now only exports reporters implementations and options types. If you need access to `TestCase`/`TestSuite` and other task related types, import them additionally from `vitest/node`. +### Coverage ignores test files even when `coverage.excludes` is overwritten. + +It is no longer possible to include test files in coverage report by overwriting `coverage.excludes`. Test files are now always excluded. + ## Migrating to Vitest 2.0 {#vitest-2} ### Default Pool is `forks` From d7c92ddbebedb3ca42f0e01033af10b50da064f4 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 16 Jan 2025 14:53:15 +0100 Subject: [PATCH 12/15] docs: more migration changes in v3 (#7261) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ari Perkkiö --- guide/migration.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/guide/migration.md b/guide/migration.md index 8d3499cd..c0322a71 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -54,6 +54,81 @@ export default defineConfig({ With the new `browser.instances` field you can also specify multiple browser configurations. +### `spy.mockReset` Now Restores the Original Implementation + +There was no good way to reset the spy to the original implementation without reaplying the spy. Now, `spy.mockReset` will reset the implementation function to the original one instead of a fake noop. + +```ts +const foo = { + bar: () => 'Hello, world!' +} + +vi.spyOn(foo, 'bar').mockImplementation(() => 'Hello, mock!') + +foo.bar() // 'Hello, mock!' + +foo.bar.mockReset() + +foo.bar() // undefined // [!code --] +foo.bar() // 'Hello, world!' // [!code ++] +``` + +### `vi.spyOn` Reuses Mock if Method is Already Mocked + +Previously, Vitest would always assign a new spy when spying on an object. This caused errors with `mockRestore` because it would restore the spy to the previous spy instead of the original function: + +```ts +vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar') +vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar') +vi.restoreAllMocks() +vi.isMockFunction(fooService.foo) // true // [!code --] +vi.isMockFunction(fooService.foo) // false // [!code ++] +``` + +### Fake Timers Defaults + +Vitest no longer provides default `fakeTimers.toFake` options. Now, Vitest will mock any timer-related API if it is available (except `nextTick`). Namely, `performance.now()` is now mocked when `vi.useFakeTimers` is called. + +```ts +vi.useFakeTimers() + +performance.now() // original // [!code --] +performance.now() // fake // [!code ++] +``` + +You can revert to the previous behaviour by specifying timers when calling `vi.useFakeTimers` or globally in the config: + +```ts +export default defineConfig({ + test: { + fakeTimers: { + toFake: ['setTimeout', 'clearTimeout', 'Date'], // [!code ++] + }, + }, +}) +``` + +### More Strict Error Equality + +Vitest now checks more properties when comparing errors via `toEqual` or `toThrowError`. Vitest now compares `name`, `message`, `cause` and `AggregateError.errors`. For `Error.cause`, the comparison is done asymmetrically: + +```ts +expect(new Error('hi', { cause: 'x' })).toEqual(new Error('hi')) // ✅ +expect(new Error('hi')).toEqual(new Error('hi', { cause: 'x' })) // ❌ +``` + +In addition to more properties check, Vitest now compares error prototypes. For example, if `TypeError` was thrown, the equality check should reference `TypeError`, not `Error`: + +```ts +expect(() => { + throw new TypeError('type error') +}) + .toThrowError(new Error('type error')) // [!code --] + .toThrowError(new TypeError('type error')) // [!code ++] +``` + +See PR for more details: [#5876](https://github.com/vitest-dev/vitest/pull/5876). + ### `Custom` Type is Deprecated API {#custom-type-is-deprecated} The `Custom` type is now an alias for the `Test` type. Note that Vitest updated the public types in 2.1 and changed exported names to `RunnerCustomCase` and `RunnerTestCase`: @@ -75,6 +150,12 @@ In the public API this type was used in custom [sequencers](/config/#sequence-se The [`onTestFinished`](/api/#ontestfinished) and [`onTestFailed`](/api/#ontestfailed) hooks previously received a test result as the first argument. Now, they receive a test context, like `beforeEach` and `afterEach`. +### Changes to the Snapshot API API {#changes-to-the-snapshot-api} + +The public Snapshot API in `@vitest/snapshot` was changed to support multiple states within a single run. See PR for more details: [#6817](https://github.com/vitest-dev/vitest/pull/6817) + +Note that this changes only affect developers using the Snapshot API directly. There were no changes to `.toMatchSnapshot` API. + ### Changes to `resolveConfig` Type Signature API {#changes-to-resolveconfig-type-signature} The [`resolveConfig`](/advanced/api/#resolveconfig) is now more useful. Instead of accepting already resolved Vite config, it now accepts a user config and returns resolved config. From 2c78cc1406eed6a26b4b9527322f7931511fe91a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:05:11 +0100 Subject: [PATCH 13/15] fix(deps): update all non-major dependencies (#7147) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Vladimir Sheremet --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 390d97f1..7fae948d 100644 --- a/package.json +++ b/package.json @@ -14,26 +14,26 @@ "generate-pwa-icons": "pwa-assets-generator" }, "dependencies": { - "@vueuse/core": "^12.1.0", + "@vueuse/core": "^12.4.0", "vue": "^3.5.12" }, "devDependencies": { "@iconify-json/carbon": "^1.2.5", "@iconify-json/logos": "^1.2.4", - "@shikijs/transformers": "^1.24.4", - "@shikijs/vitepress-twoslash": "^1.24.4", - "@unocss/reset": "^0.65.2", + "@shikijs/transformers": "^1.27.2", + "@shikijs/vitepress-twoslash": "^1.27.2", + "@unocss/reset": "^0.65.4", "@vite-pwa/assets-generator": "^0.2.6", "@vite-pwa/vitepress": "^0.5.3", "@vitejs/plugin-vue": "^5.2.1", "https-localhost": "^4.7.1", "tinyglobby": "^0.2.10", - "unocss": "^0.65.2", + "unocss": "^0.65.4", "unplugin-vue-components": "^0.28.0", "vite": "^5.2.8", "vite-plugin-pwa": "^0.21.1", "vitepress": "^1.5.0", - "vitepress-plugin-group-icons": "^1.3.2", + "vitepress-plugin-group-icons": "^1.3.4", "vitepress-plugin-tabs": "^0.5.0", "workbox-window": "^7.3.0" } From 1dc5f19ec509d80bc43661a9c070b048c5a4c41f Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 16 Jan 2025 16:29:43 +0100 Subject: [PATCH 14/15] docs: add v2 to the list of versions (#7263) --- .vitepress/config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 2ead4fa0..6ee8f3c4 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -191,6 +191,10 @@ export default ({ mode }: { mode: string }) => { text: 'v1.x', link: 'https://v1.vitest.dev/', }, + { + text: 'v2.x', + link: 'https://v2.vitest.dev/', + }, ], }, ], From db046fcb24c02a5283628467ca6300d607779be3 Mon Sep 17 00:00:00 2001 From: Pjotr Savitski Date: Thu, 16 Jan 2025 21:19:13 +0200 Subject: [PATCH 15/15] docs: changed readHelloWorld in exampele 3 to return a string (#7272) --- guide/mocking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/mocking.md b/guide/mocking.md index dab36728..64ec3066 100644 --- a/guide/mocking.md +++ b/guide/mocking.md @@ -385,7 +385,7 @@ module.exports = fs.promises import { readFileSync } from 'node:fs' export function readHelloWorld(path) { - return readFileSync(path) + return readFileSync(path, 'utf-8') } ```